{"id":2170,"date":"2020-05-18T21:07:31","date_gmt":"2020-05-18T18:07:31","guid":{"rendered":"https:\/\/upcloud.com\/global\/us\/resources\/tutorials\/deploy-dockerized-apps-kubernetes-centos-8\/"},"modified":"2026-04-23T13:52:39","modified_gmt":"2026-04-23T12:52:39","slug":"deploy-dockerized-apps-kubernetes-centos-8","status":"publish","type":"tutorial","link":"https:\/\/upcloud.com\/global\/resources\/tutorials\/deploy-dockerized-apps-kubernetes-centos-8\/","title":{"rendered":"How to deploy dockerized apps to Kubernetes on CentOS 8"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Kubernetes is a popular container orchestration system that lets you automate application deployment, scaling and management tasks via simple command line calls. This guide describes how to build and deploy a simple dockerized web app to a Kubernetes cluster on CentOS 8.<\/p>\n\n\n\n\n\n<p class=\"wp-block-paragraph\">This guide is divided into three main parts:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">1. Testing out containers<br>2. Building a simple dockerized app using Dockerfile<br>3. Deploying the app to Kubernetes<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you do not yet have a running Kubernetes cluster, have a look at our earlier tutorial on <a href=\"https:\/\/upcloud.com\/global\/resources\/tutorials\/install-kubernetes-cluster-centos-8\/\" target=\"_blank\" rel=\"noreferrer noopener\">how to install Kubernetes on CentOS 8<\/a>.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/signup.upcloud.com\/\">Test hosting on UpCloud!<\/a><\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Testing out containers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s start by running a simple docker app to test the container platform.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">1. Run hello-world app with the following command:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker run hello-world<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see the app print \u201cHello from Docker!\u201d to your terminal towards the end of the output.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Running a known container pulls the required image from a public registry and saves it on your docker server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2. Check the list of docker images<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker images<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">After running the hello-world app once, it should show up on the images list.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">REPOSITORY    TAG      IMAGE ID       CREATED         SIZE\nhello-world   latest   fce289e99eb9   15 months ago   1.84kB<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now let\u2019s try a more advanced docker app<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">1. Run the Ubuntu docker app in interactive mode.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker run -it ubuntu bash<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On success, you\u2019ll be brought directly to Ubuntu prompt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2. Test the internet connection from inside docker app by running a command that requires network access.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On Ubuntu\u2019s terminal prompt, run the following to check it has an internet connection.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">apt update<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On success, you should see something like the below.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Get:1&nbsp;http:\/\/security.ubuntu.com\/ubuntu&nbsp;bionic-security InRelease [88.7 kB]\nGet:2&nbsp;http:\/\/archive.ubuntu.com\/ubuntu&nbsp;bionic InRelease [242 kB]\n...\nGet:17&nbsp;http:\/\/archive.ubuntu.com\/ubuntu&nbsp;bionic-backports\/main amd64 Packages [2496 B]\nGet:18&nbsp;http:\/\/archive.ubuntu.com\/ubuntu&nbsp;bionic-backports\/universe amd64 Packages [4247 B] Fetched 17.7 MB in 5s (3439 kB\/s)<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then stop the Ubuntu container with the following command.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">exit<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In case you cannot access the internet, you might see something similar to the error underneath.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Err:1&nbsp;http:\/\/security.ubuntu.com\/ubuntu&nbsp;bionic-security InRelease Temporary failure resolving 'security.ubuntu.com' Err:2&nbsp;<a href=\"http:\/\/archive.ubuntu.com\/ubuntu\" target=\"_blank\" rel=\"noopener\">http:\/\/archive.ubuntu.com\/ubuntu<\/a>&nbsp;bionic InRelease Temporary failure resolving 'archive.ubuntu.com' ...<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">To fix this, make sure IP masquerade is enabled at the firewall and then try again.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">firewall-cmd --add-masquerade --permanent\nfirewall-cmd --reload<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">OK, we\u2019ve completed the first part, now let us move on to the next step.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Building a simple dockerized app using Dockerfile<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dockerizing apps is a great way of creating consistent and reliable environments for many tasks regardless of the underlying operating system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For this example, we are going to make a simple Golang web server that takes input via the server URL and prints out a hello message.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">1. Start by creating a new directory named \u201cgoserver\u201d<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">mkdir goserver<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">2. Create a file named \u201cmain.go\u201d in the goserver directory<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">vi goserver\/main.go<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enter the following code, then save the file.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">package main\nimport ( \n   \"fmt\"\n   \"net\/http\"\n)\nfunc main() {\n   http.HandleFunc(\"\/\", HelloServer)\n   http.ListenAndServe(\":8080\", nil)\n}\nfunc HelloServer(w http.ResponseWriter, r *http.Request) {\n   fmt.Fprintf(w, \"Hello, %s!n\", r.URL.Path[1:])\n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">3. Create another new file named \u201cDockerfile\u201d with the following command.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">vi Dockerfile<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then add the following content to that file, save and exit the text editor.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">FROM golang:alpine as builder\nWORKDIR \/build\nCOPY \/goserver .\nRUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags \"-static\"' -o main .\nFROM scratch\nCOPY --from=builder \/build\/main \/app\/\nWORKDIR \/app\nENV PORT=8080\nCMD [\".\/main\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">4. Next, build a docker image based on our goserver.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker build -f Dockerfile -t goserver .<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">5. Additionally, you may wish to clean unused images from the build process.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker system prune -f<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">6. Then check that your web app was added to the images list.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker images<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see a line such as in the example below.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">REPOSITORY   TAG      IMAGE ID       CREATED          SIZE\ngoserver    latest   6c0fb70f56fe   55 seconds ago   7.41MB<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">7. Afterwards, test run the web app.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker run -d -p 8090:8080 --name goserver goserver<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">8. You can verify that the deployment was successful by checking the running images.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker ps -f name=goserver<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see the \u201cgoserver\u201d container running with the status \u201cUp\u201d.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CONTAINER ID   IMAGE       COMMAND    CREATED          STATUS          PORTS                    NAMES\na16ebdf117b9   goserver   \".\/main\"   54 seconds ago   Up 53 seconds   0.0.0.0:8090-&gt;8080\/tcp   goserver<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Your web app is now reachable both locally and over the internet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">9. Call it on the command line, for example by using curl with the command below.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">curl http:\/\/localhost:8090\/Joe<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should have curl available but if not, install curl using <tt>sudo dnf install curl<\/tt><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can also open the page in your web browser. Replace the <span style=\"color: #ff0000;\">public-ip-address<\/span> with the IP of your Master node.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><tt>http:\/\/public-ip-address:8090\/Joe<\/tt><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You should see the following response.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hello, Joe!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">12. Once done, stop the web server and remove the image.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker stop goserver &amp;&amp; docker rm goserver<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Congratulations, you should now have an idea of what\u2019s included in making simple dockerized apps.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Deploying a dockerized app to Kubernetes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We\u2019ve now tested out the container platform and built our own dockerized web app, so the last thing to do is to deploy it on our Kubernetes cluster.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This part is referring to the Kubernetes configuration installed in our <a href=\"https:\/\/upcloud.com\/global\/resources\/tutorials\/install-kubernetes-cluster-centos-8\/\" target=\"_blank\" rel=\"noreferrer noopener\">previous tutorial<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">1. Create the following configuration file on the <strong>master node<\/strong>. Replace the <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span> with the private IP address of your master node.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">cat &gt; \/etc\/docker\/daemon.json &lt;&lt;EOF\n{\n   \"log-driver\": \"json-file\",\n   \"log-opts\": {\n      \"max-size\": \"100m\"\n   },\n   \"storage-driver\": \"overlay2\",\n   \"storage-opts\": [\n      \"overlay2.override_kernel_check=true\"\n   ],\n   \"insecure-registries\": [ \"<span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span>:5000\" ]\n}\nEOF<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">2. Add the exception for the registry also on all <strong>worker nodes<\/strong>. Again, replace the <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span> with the private IP address of your master node.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">cat &gt; \/etc\/docker\/daemon.json &lt;&lt;EOF\n{\n   \"insecure-registries\": [ \"<span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span>:5000\" ]\n}\nEOF<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then restart Docker on <strong>all nodes<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">systemctl restart docker<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Continue with the rest of the steps on the <strong>master node only<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We are going to be using a private registry container for storing and distributing our dockerized app in our cluster.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3. Get the latest version of the docker registry.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker pull registry:2<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">4. Run the docker registry on port 5000 using the master node\u2019s private IP address. Replace the <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span> with the correct address.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker run -dit --restart=always -p <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span>:5000:5000 --name registry -e REGISTRY_STORAGE_DELETE_ENABLED=true registry:2<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">5. Next, create a deployment artefact for our \u201cgoserver\u201d app.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Please be aware that indentation matters. This artefact is using two spaces for indentation.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">vi deployment.yaml<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enter the following content and again replace the <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span> with the correct address.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">apiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: goserver\nspec:\n  selector:\n    matchLabels:\n      app: goserver\n  replicas: 2\n  revisionHistoryLimit: 0\n  progressDeadlineSeconds: 30\n  strategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxSurge: 2\n      maxUnavailable: 2\n  template:\n    metadata:\n      labels:\n        app: goserver\n    spec:\n      containers:\n      - name: goserver\n        image: <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span>:5000\/goserver:v1\n        ports:\n        - hostPort: 8090\n          containerPort: 8080\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app: goserver\n  name: goserver\nspec:\n  selector:\n    app: goserver\n  ports:\n  - protocol: TCP\n    port: 8090\n    targetPort: 8080\n  type: ClusterIP<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">6. Tag the docker app to add the private registry address and port. As before, replace the <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span> with the private IP address of your master node.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker tag goserver:latest <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span>:5000\/goserver:v1<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">7. Then check to make sure the tagged app was created.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker images<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It should show up on the list.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">8. Push the tagged app to the private registry so it can be pulled by Kubernetes deployment. Again, replace the <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span> with the private IP address of your master node.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker push <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span>:5000\/goserver:v1<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">9. Check that the tagged app was successfully pushed to the registry by querying the registry. Replace the <span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span> with the private IP address of your master node.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">curl -s -X GET http:\/\/<span style=\"color: #ff0000;\">&lt;master_private_IP&gt;<\/span>:5000\/v2\/goserver\/tags\/list<\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">{\"name\":\"goserver\",\"tags\":[\"v1\"]}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now that we have set up our private repository and pushed the docker image to it, it can be used to deploy the app onto our Kubernetes cluster.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">10. Deploy our goserver using the command below.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">kubectl apply -f deployment.yaml<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">11. Check that the deployment rollout succeeded.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">kubectl rollout status deployment\/goserver<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">12. You can also check that the pods are up and running.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">kubectl get pods<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">13. You should now be able to access the web server on both master and worker nodes.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">curl http:\/\/localhost:8090\/Joe<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Or open browser to http:\/\/&lt;public-IP-address&gt;:8090\/Joe<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Replace the <span style=\"color: #ff0000;\">&lt;public-IP-address&gt;<\/span> with either the public IP address of your master or worker node.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You should see the familiar greeting.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hello, Joe!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">14. When you wish to delete the test app, revert the deployment with the following command.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">kubectl delete -f deployment.yaml<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Done!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">All done!<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When you need to update \u201cgoserver\u201d codes, you need to dockerized again. Then you also need to push it to the registry with different tags (e.g. v2).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s all folks, happy dockerizing!<\/p>\n","protected":false},"author":34,"featured_media":14966,"comment_status":"open","ping_status":"closed","template":"","community-category":[223,229],"class_list":["post-2170","tutorial","type-tutorial","status-publish","has-post-thumbnail","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/2170","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial"}],"about":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/types\/tutorial"}],"author":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/users\/34"}],"replies":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/comments?post=2170"}],"version-history":[{"count":1,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/2170\/revisions"}],"predecessor-version":[{"id":6418,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/2170\/revisions\/6418"}],"wp:attachment":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/media?parent=2170"}],"wp:term":[{"taxonomy":"community-category","embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/community-category?post=2170"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}