Posting a Custom Image to Docker Hub

Welcome to 2020! I hope the new year finds everyone in good spirits and ready to continue listening to me babble about my struggles with technology.

So far, the focus has been on using default Docker images for our builds. This is great if you plan to deploy stock instances and only need to serve custom content with some minor configuration tweaks. Note that we were able to make configuration changes using a configMap yaml. What if you needed Nginx modules that weren’t already installed in the base image? Sure, you could come up with some funky CMD statement in your yaml file that tells Kubernetes to install the modules. Of course, that’ll take some time for the pod to be available while it boots up and runs through the install steps. This will also defeat the purpose of what I’m attempting to show you too 🙂

The focus of this article is simple. We’re going to setup a Docker Hub account and build a custom Nginx image to post there. From there, there are some future articles to help us use this new found knowledge to do some cool stuff.

Let’s stop the babble and start the fun!

Creating a Docker Hub Account

This is pretty straight forward so we’ll cover it briefly.

  1. Go to
  2. Click the Sign up for Docker Hub button
  3. Enter your information
  4. Sign up
  5. Wait for the verification Email from Docker
  6. Verify your email via the verification email
  7. Sign in


Create a Docker Hub Repository

Now that you have a Docker Hub account, you’ll want to create a repo to be able to store your custom docker image. Assuming that you are still signed in from the steps above, you should see a Create a Repository button:

If you don’t see the Create a Repository button, worry not, you can get there by clicking on the Repositories link on the top menu and then the Create Repository button:

On the resulting Create Repository screen, let’s add in some details such as below:

You may call the repo whatever you want and feel free to give it a description. For now, we’re going to make this a public repo. Once you have this information filled out, scroll to the bottom and click Create. You should now see something similar to the below:

Make note of the docker push command in the black background box on the right. In my case, it is

docker push algattblog/testnginximage:tagname

We’ll need this later when we build our custom image.

Configuring Our Custom Nginx Docker Image

In order to keep everything in one place and keep things backed up, we’ll be building this out within our previously defined Git Repo. It’s a private repo so reasonably protected and Github is a really nice place to maintain our backup. So the first step will be to make sure we’re in the root of our repo and we’ll make a new directory to store this image.

$ mkdir nginxdocker

From there, we’ll change into the directory so that we can start with our Dockerfile

$ cd nginxdocker/

Now let’s create a new Dockerfile that looks like the following:

FROM ubuntu


RUN apt-get update 
    && apt-get install -y nginx libnginx-mod-http-lua libnginx-mod-http-subs-filter software-properties-common
    && add-apt-repository -y universe 
    && add-apt-repository -y ppa:certbot/certbot 
    && apt-get update 
    && apt-get -y install certbot python-certbot-nginx 
    && apt-get clean 
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

COPY ./conf/nginx.conf /etc/nginx/nginx.conf
COPY ./conf/site.conf /etc/nginx/sites-available/default


CMD ["nginx"]

Let’s see what this does…First, we’re going to build this new Docker using ubuntu as our base image. From there, we’re going to install nginx, libnginx-mod-http-lua, libnginx-mod-http-subs-filter, and software-properties-common. We’re installing software-properties-common so that we can add the certbot repo and then add certbot. We’re going to also copy over some custom Nginx configuration files so we won’t need to leverage our configMap anymore. We make sure ports 80 and 443 are exposed to the running container. Finally, the container should run the “nginx” command to start the nginx server.

Next, we’ll want to create those files referenced by the COPY commands. We start by creating the conf directory with and then change into the directory:

$ mkdir conf
$ cd conf

Create the nginx.conf file with the following (Basically, we’re defining a custom log format):

user www-data;
worker_processes auto;
pid /run/;

events {
        worker_connections 768;

http {
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        include /etc/nginx/mime.types;
        default_type text/html;
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
        log_format  graylog2_format  '$remote_addr $request_method "$request_uri" $status $bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for" "$http_if_none_match"';
        gzip on;
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;

daemon off;

The MOST important item at the bottom of this file would be the daemon off; statement. Without this, our container will start run nginx and then COMPLETE and stop. We want nginx to run in the foreground and not background as a daemon so this is why this is here. Now create the referenced site.conf file.

server {
    listen       80;
    server_name  localhost;
    access_log /var/log/nginx/access.log graylog2_format;
    error_log /var/log/nginx/error.log graylog2_format;

    location / {
        root   /usr/share/nginx/www/html;
        index  index.html index.htm;

    error_page   500 502 503 504  /50x.html;

    location = /50x.html {
        root   /usr/share/nginx/www/html;

  location ~ .php$ {
      root /usr/share/nginx/www/html;
      try_files $uri =404;
      fastcgi_split_path_info ^(.+.php)(/.+)$;
      fastcgi_pass phpfpm:9000;
      fastcgi_index index.php;
      include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param PATH_INFO $fastcgi_path_info;

This looks good so let’s first save our changes and commit them to our repo.

 3 files changed, 23 insertions(+), 37 deletions(-)$ cd ../..
$ git add .
$ git commit -a
[master ade43da] Adding in our stuff
 Committer: Scott <[email protected]>
 3 files changed, 23 insertions(+), 37 deletions(-)
 rewrite nginxdocker/conf/site.conf (99%)
 delete mode 100644 nginxdocker/conf/test
$ git push origin master
Enter passphrase for key '/Users/scott/.ssh/id_rsa': 
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 754 bytes | 754.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
   97b7a1c..ade43da  master -> master

Now that we’ve got that squared away, it’s onto the next step!

Building our Docker Image and Publishing it

This is the easy part as we just watch it run. Make sure we’re in the directory that contains our Dockerfile and then we’ll run the build command:

$ cd nginxdocker/
 imacs-imac:nginxdocker scott$ docker build -t algattblog/testnginximage:latest .
 Sending build context to Docker daemon  6.144kB
 Step 1/8 : FROM ubuntu
 latest: Pulling from library/ubuntu
 2746a4a261c9: Pull complete 
 4c1d20cdee96: Pull complete 
 0d3160e1d0de: Pull complete 
 c8e37668deea: Pull complete 
 Digest: sha256:250cc6f3f3ffc5cdaa9d8f4946ac79821aafb4d3afc93928f0de9336eba21aa4
 Status: Downloaded newer image for ubuntu:latest
  ---> 549b9b86cb8d
 Step 2/8 : MAINTAINER Scott Algatt
  ---> Running in ff6d8459f56b
 Removing intermediate container ff6d8459f56b
  ---> 666acba43494
 Step 3/8 : RUN apt-get update     && apt-get install -y nginx libnginx-mod-http-lua libnginx-mod-http-subs-filter software-properties-common    && add-apt-repository -y universe     && add-apt-repository -y ppa:certbot/certbot     && apt-get update     && apt-get -y install certbot python-certbot-nginx     && apt-get clean     && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
  ---> Running in acfefd676a08
 Get:1 bionic-security InRelease [88.7 kB]
 Get:2 bionic InRelease [242 kB]
 Setting up python-certbot-nginx (0.31.0-1+ubuntu18.04.1+certbot+1) ...
 Removing intermediate container acfefd676a08
  ---> 09301061d312
 Step 4/8 : COPY ./conf/nginx.conf /etc/nginx/nginx.conf
  ---> c82b8d22e6a0
 Step 5/8 : COPY ./conf/site.conf /etc/nginx/sites-available/default
  ---> 841e6ecfc3d9
 Step 6/8 : EXPOSE 80
  ---> Running in f2f36c350457
 Removing intermediate container f2f36c350457
  ---> 79af7e01f9c0
 Step 7/8 : EXPOSE 443
  ---> Running in 9d6a8dcdba31
 Removing intermediate container 9d6a8dcdba31
  ---> a534c821c51b
 Step 8/8 : CMD ["nginx"]
  ---> Running in 82ceccd20644
 Removing intermediate container 82ceccd20644
  ---> 6728616336a3
 Successfully built 6728616336a3
 Successfully tagged algattblog/testnginximage:latest

Next, we need to publish it on Docker Hub (remember that push command from earlier?):

$ docker push algattblog/testnginximage:latest
 The push refers to repository []
 010c4615edf3: Pushed 
 98c06aef3fd3: Pushed 
 229f4ffc7b88: Pushed 
 918efb8f161b: Mounted from library/ubuntu 
 27dd43ea46a8: Mounted from library/ubuntu 
 9f3bfcc4a1a8: Mounted from library/ubuntu 
 2dc9f76fb25b: Mounted from library/ubuntu 
 latest: digest: sha256:4545730a7dd5b5818f0ce9a78666f40ea9a864198665022dc29000a34cc4b402 size: 1778

Testing the New Image in our Cluster

At this point, we’ve got our newly created image uploaded to Docker Hub. The next step is to test it out and see if it works in our cluster. In order to do that, we’ll want to change the image in our webserver.yaml file to reference the newly created image:

        image: algattblog/testnginximage:latest

Now let’s apply the updated deployment:

# kubectl apply -f webserver.yaml 
 configmap/webserver-config unchanged
 service/webserver unchanged
 deployment.apps/webserver configured

Wait for it to build the new webserver pod by keeping an eye on kubectl:

# kubectl get pod
 NAME                         READY   STATUS    RESTARTS   AGE
 phpfpm-7b8d87955c-rps2w      2/2     Running   0          3d1h
 webserver-6b577db595-wgwwb   2/2     Running   0          25s

Looks like it’s up and running so now let’s check and see if we can test to make sure everything looks good. We’ll start by connecting to the webserver, installing curl, and making sure everything works again.

# kubectl exec -it webserver-6b577db595-wgwwb /bin/bash -c webserver
 groups: cannot find name for group ID 65533
 root@webserver-6b577db595-wgwwb:/# apt update     
 Get:1 bionic-security InRelease [88.7 kB]
 Get:2 bionic InRelease [242 kB]           
 Setting up libcurl4:amd64 (7.58.0-2ubuntu3.8) ...
 Setting up curl (7.58.0-2ubuntu3.8) ...
 Processing triggers for libc-bin (2.27-3ubuntu1) ...
 root@webserver-6b577db595-wgwwb:/# curl localhost
 hello world! Everything must be cleaned up at this point
 root@webserver-6b577db595-wgwwb:/# curl localhost/index.php
 hello world from php

We can ignore the groups error for now. Some idiot left out instructions regarding fixing that but not a biggie. Looks we’re serving content properly. Let’s check our nginx configuration to confirm it’s also running the correct config:

root@webserver-6b577db595-wgwwb:/# cat /etc/nginx/nginx.conf 
 user www-data;
  log_format  graylog2_format  '$remote_addr $request_method "$request_uri" $status $bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for" "$http_if_none_match"';
  gzip on;
  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
 daemon off;

Confirmed! This looks great! You’ve now got a custom Docker image that you can use and expand upon. I know I left you hanging here but if I continued to build my attention span couldn’t tolerate typing anymore. More to come….