{"id":5481,"date":"2026-10-27T10:50:25","date_gmt":"2026-10-27T10:50:25","guid":{"rendered":"https:\/\/geekmungus.co.uk\/?p=5481"},"modified":"2026-10-27T10:50:25","modified_gmt":"2026-10-27T10:50:25","slug":"kubernetes-test-lab-part-3-using-docker-to-create-container-image-and-deploy","status":"publish","type":"post","link":"https:\/\/geekmungus.co.uk\/?p=5481","title":{"rendered":"Kubernetes Test Lab &#8211; Part 3 &#8211; Using Docker to Create Container Image and Deploy"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">We&#8217;re using Containerd for to run the containers within our Kubernetes environment. But if we want to quickly spin up some containers for testing, or more likely create our own container images, we can use Docker. Before we can start to create our own container images, we need to install Docker and then register it with a DockerHub account, so we can push our created images to DockerHub, to then start up on our Kubernetes cluster.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We&#8217;re going to do a simple example where we&#8217;ll create a Container Image with NGINX, and a custom index.html page to make it obvious the container has indeed been customised somewhat.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Install Docker (Option 1)<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">On our Management Workstation we&#8217;ll install Docker, so we&#8217;ll install it using the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\nsudo apt install docker.io -y\n\nsudo systemctl enable docker\nsudo systemctl start docker<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Configure for Run as Non-Root User<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To ensure you can run as a non-root user, this will add your current user designated by the $USER environment variable to the group &#8220;docker&#8221;.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo groupadd docker\nsudo usermod -aG docker $USER<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now you can either log out of your session and back in again, or you may be able to run the following to force Linux to reenumerate your user&#8217;s group membership.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>newgrp docker<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verify and Test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Verify you can indeed run as as a non-root user:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run hello-world<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you get an output, that shows you can successfully run docker containers under your normal user account.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Install Docker (Option 2)<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Add Docker&#8217;s official GPG key:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\nsudo apt install ca-certificates curl\nsudo install -m 0755 -d \/etc\/apt\/keyrings\nsudo curl -fsSL https:\/\/download.docker.com\/linux\/ubuntu\/gpg -o \/etc\/apt\/keyrings\/docker.asc\nsudo chmod a+r \/etc\/apt\/keyrings\/docker.asc<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Add the repository to APT sources:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo tee \/etc\/apt\/sources.list.d\/docker.sources &lt;&lt;EOF\nTypes: deb\nURIs: https:\/\/download.docker.com\/linux\/ubuntu\nSuites: $(. \/etc\/os-release &amp;&amp; echo \"${UBUNTU_CODENAME:-$VERSION_CODENAME}\")\nComponents: stable\nArchitectures: $(dpkg --print-architecture)\nSigned-By: \/etc\/apt\/keyrings\/docker.asc\nEOF\n\nsudo apt update<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now install Docker, we&#8217;re going to use the latest version, see the Docker documentation about the installation of a specific version if you so require a specific version.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once installed, check it is running with the following and required start it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status docker\nsudo systemctl start docker<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Configure for Run as Non-Root User<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To ensure you can run as a non-root user, this will add your current user designated by the $USER environment variable to the group &#8220;docker&#8221;.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo groupadd docker\nsudo usermod -aG docker $USER<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now you can either log out of your session and back in again, or you may be able to run the following to force Linux to renumerate your user&#8217;s group membership.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>newgrp docker<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verify and Test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Verify you can indeed run as as a non-root user:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run hello-world<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you get an output, that shows you can successfully run docker containers under your normal user account.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Login to DockerHub Account<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">If you were to create a new Container Image and then build it. The docker image is only stored in our local Docker repository on this Management Workstation, if we want to use this Docker image elsewhere we can, but we need to push it up to our DockerHub.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;m assuming you have a DockerHub account already setup, so we&#8217;ll now connect it up from the Management Workstation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd ~\ndocker login -u &lt;username><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When prompted, you&#8217;ll want to create a PAT instead. Login to the web interface, under the Profile Settings, see Personal Access Tokens, and generate one, giving it a suitable name. I&#8217;m setting it to be <strong>Read, Write, Delete<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"705\" height=\"456\" src=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-14.png\" alt=\"\" class=\"wp-image-5482\" srcset=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-14.png 705w, https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-14-300x194.png 300w\" sizes=\"auto, (max-width: 705px) 100vw, 705px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Paste the generated PAT into the password prompt on the command line.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">All being well you should now find you are logged in:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"626\" height=\"244\" src=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-15.png\" alt=\"\" class=\"wp-image-5483\" srcset=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-15.png 626w, https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-15-300x117.png 300w\" sizes=\"auto, (max-width: 626px) 100vw, 626px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You should now be ready to push your image to the DockerHub.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Create and Build a Container Image<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">You may want to have a Git repository cloned to your Development Workstation to store all your Dockerfile definitions in.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create Dockerfile Definition and Content<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We&#8217;ll create a simple NGINX image, so we first create a directory to hold the Docker files and another directory within that called &#8220;nginx-custom&#8221; which will actually hold the Dockerfile and our customised index.html file.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir docker\ncd docker\nmkdir nginx-custom<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create two files as follows:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Dockerfile<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM nginx:1.27.0\n\nRUN apt-get update\nRUN apt-get install vim -y\n\nCOPY index.html \/usr\/share\/nginx\/html\/index.html\nRUN chown nginx:nginx \/usr\/share\/nginx\/html\/index.html<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>index.html<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;!DOCTYPE html>\n&lt;html>\n&lt;head>\n&lt;title>Welcome to nginx!&lt;\/title>\n&lt;style>\nhtml { color-scheme: light dark; }\nbody { width: 35em; margin: 0 auto;\nfont-family: Tahoma, Verdana, Arial, sans-serif; }\n&lt;\/style>\n&lt;\/head>\n&lt;body>\n&lt;h1>Welcome to NGINX!&lt;\/h1>\n&lt;p>Wellcome to NGINX! This is a customised default image, with a replaced index.html file.&lt;\/p>\n&lt;\/body>\n&lt;\/html><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Build Image<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We build an image based on a Dockerfile manifest we just created:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker build -t geekmungus\/nginx-custom:v1.0.0 .<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check that the Container Image is now within the local Docker image repository with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker image ls<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"599\" height=\"107\" src=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-17.png\" alt=\"\" class=\"wp-image-5485\" srcset=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-17.png 599w, https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-17-300x54.png 300w\" sizes=\"auto, (max-width: 599px) 100vw, 599px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Lovely, so now we have it, let&#8217;s quickly try to run it locally, before we push the image to DockerHub.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify and Test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s quickly test it works and give us the result we expect with the following, this will automatically remove the container when stopped, so we don&#8217;t have to clean it up.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run --rm -d --name nginx-custom -p 80:80 geekmungus\/nginx-custom:v1.0.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The container will now be running and listening on Port 80. To verify it works as expected, we CURL for the <a href=\"http:\/\/localhost\/\">http:\/\/localhost<\/a> (i.e. port 80).<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"703\" height=\"278\" src=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-18.png\" alt=\"\" class=\"wp-image-5486\" srcset=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-18.png 703w, https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-18-300x119.png 300w\" sizes=\"auto, (max-width: 703px) 100vw, 703px\" \/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Push Container Image to DockerHub<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s push the image we just created up to the DockerHub, we&#8217;re already logged into DockerHub, so all we need to do is&#8230;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker push geekmungus\/nginx-custom:v1.0.0<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"763\" height=\"358\" src=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-19.png\" alt=\"\" class=\"wp-image-5487\" srcset=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-19.png 763w, https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-19-300x141.png 300w\" sizes=\"auto, (max-width: 763px) 100vw, 763px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The image is now pushed to DockerHub, depending on the privacy settings of your DockerHub repositories, its now visible and able to be pulled by the Kubernetes Cluster to its own image registry, so it is able to run this image, we&#8217;ll try that in a moment.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Use Customised Image (from Repository) in Kubernetes Deployment<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">So now we have pushed our customised image to DockerHub. We can now actually deploy our application onto our Kubernetes environment. We first need to create a Kubernetes manifest file and apply it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Notice that within the &#8220;containers&#8221; section of the specification, the &#8220;image&#8221; is set to &#8220;geekmungus\/nginx-custom:v1.0.0&#8221;, thus specifying the container image we just created and tested (with Docker) a few moments ago.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>nginx-custom-simple.yaml<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Deployment Definition\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: nginx-custom\n  labels:\n    app: nginx-custom\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: nginx-custom\n  template:\n    metadata:\n      labels:\n        app: nginx-custom\n    spec:\n      containers:\n        - name: nginx-custom\n          image: geekmungus\/nginx-custom:v1.0.0\n          ports:\n            - containerPort: 80\n          resources:\n            requests:\n              cpu: 256m\n              memory: 128Mi\n            limits:\n              cpu: 512m\n              memory: 256Mi\n---\n# Service Definition\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-custom-service\n  labels:\n    app: nginx-custom\nspec: \n  type: LoadBalancer\n  allocateLoadBalancerNodePorts: true # Requires a redeploy to add or remove.\n  selector:\n    app: nginx-custom\n  ports:\n  - port: 80\n    targetPort: 80<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now we apply it as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kubectl apply -f nginx-custom-simple.yaml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">We can then check it is running as expected with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kubectl get pods,svc<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And as we can see in the output below, we can see the &#8220;nginx-custom-service&#8221; which has a LoadBalancer and an external IP address 192.168.101.161 (in this example). Ignore the other things running.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"688\" height=\"220\" src=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-20.png\" alt=\"\" class=\"wp-image-5488\" srcset=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-20.png 688w, https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-20-300x96.png 300w\" sizes=\"auto, (max-width: 688px) 100vw, 688px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">If we now try to access the service with CURL, we can indeed see its running our customised image.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl http:\/\/192.168.101.161<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">We can indeed see our customised image has been used and is indeed running.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"686\" height=\"268\" src=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-21.png\" alt=\"\" class=\"wp-image-5489\" srcset=\"https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-21.png 686w, https:\/\/geekmungus.co.uk\/wp-content\/uploads\/2026\/10\/image-21-300x117.png 300w\" sizes=\"auto, (max-width: 686px) 100vw, 686px\" \/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Although a simple example, the above gives you the flavour of the kind of approach you need to take when you build and deploy an application to your Kubernetes cluster. Although in the real world you would have built and configured an automated process for this, so that you would be able to create, test and deploy images within some form of a pipeline so that manual steps would not be required.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With the basis of the above, we are now good to begin to move onto our topics within the series.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Additional Information\/Further Reading<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/docs.docker.com\/engine\/install\/ubuntu\">https:\/\/docs.docker.com\/engine\/install\/ubuntu<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>We&#8217;re using Containerd for to run the containers within our Kubernetes environment. But if we want to quickly spin up some containers for testing, or more likely create our own container images, we can use Docker. Before we can start to create our own container images, we need to install Docker and then register it &#8230; <a title=\"Kubernetes Test Lab &#8211; Part 3 &#8211; Using Docker to Create Container Image and Deploy\" class=\"read-more\" href=\"https:\/\/geekmungus.co.uk\/?p=5481\" aria-label=\"Read more about Kubernetes Test Lab &#8211; Part 3 &#8211; Using Docker to Create Container Image and Deploy\">Read more<\/a><\/p>\n","protected":false},"author":4,"featured_media":4850,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-5481","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-kubernetes"],"_links":{"self":[{"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/posts\/5481","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5481"}],"version-history":[{"count":1,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/posts\/5481\/revisions"}],"predecessor-version":[{"id":5490,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/posts\/5481\/revisions\/5490"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/media\/4850"}],"wp:attachment":[{"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5481"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5481"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5481"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}