Containers 101, Part 2: Building a Container with “docker build” and Pushing it To a Docker Repo for Use with Kubernetes

How’s that for a searchable title? In your face, SEO consultants!

In Part 1, we did some basic tasks with Containers: We installed docker, launched a container, and got into its ssh session.

But that was using a container built by someone else, in that case the VMware PowerCLI image.

Here, we are kicking it up a notch by customizing a docker image with docker build, then we’ll upload it to the docker hub registry. But keep in mind that what we do here can also be modified to pushing an image to a local harbor repository, an important task for use with Kubernetes.

Requirement: Docker Hub Account

For this to work, you will need a Docker Hub Account. If you don’t have a Docker Hub account, go ahead and make one now.

Then create a repo there and note its name. It’s pretty straightforward. You will need this so you can upload (push) your image later.

Whatever your docker hub repo name is, I will refer to it later as dockerhubreponame.

A Friendly Reminder: Containers are Supposed to Be Lightweight

Remember that the idea behind containers is that they are lightweight. Only install what you need, and only what you need. Let me count the reasons:

  1. The more you install, the larger the image. Larger images can take longer to rollout. If it’s a situation where seconds count, like with . . . oh I don’t know . . . outages, larger images can cost precious time.
  2. The more you install, the more software that needs to be maintained. You know you’ll have to keep all of this stuff up to date right? The more software on the container the more conflicts and testing that will need to take place, leading to possibly longer dev cycles. So yeah, there’s that.
  3. The more you install, the less secure the image. More applications = more security risks. Your security team will thank you.

Creating Your Own Customized Container Using a Dockerfile

Before we dive in, one option for creating your container would be to create a base image. I have never done that myself. My images have always been based on other people’s images, which is called “creating an image from a parent image”. Here, I will be using a parent image, the vmware/powerclicore image from Docker Hub.

A second consideration: be aware of docker “layers“. For the most part, if you keep your images simple and lightweight, it shouldn’t be a problem, but if you get into more complex docker builds, layers are a big deal.

Simply stated, every command or change you apply in a Dockerfile can add a layer to the parent image. If you are not careful, you can add unnecessary layers thereby increasing the size of the image. The most important link I can give you is Docker’s Best Practices for Writing Dockerfiles.

Dockerfiles Demystified and Installing Software on Your Container

SPOILER ALERT: All you are doing with a Dockerfile is creating a bash script with special key words that will additionally install/configure things into a docker image that are not included in the parent image.

That’s it.

Let’s create a folder where we can isolate all of our docker work:

#> mkdir dockerfun ; cd dockerfun

A couple of considerations:

  1. Of course, check to see if what you are looking for is already on the image. If it’s already there, then your job is done!
  2. Consider how applications are run in the container. By default, when I launch the vmware/powerclicore container, it puts me right into pwsh. However, let’s say I want to install some tools like nmap to use for troubleshooting. Or I want to make sure a git repo is available for me on the image at launch. You will need to figure this out in the container first.

Here’s What I Do:

The way I customize my container is to launch the container and perform the actions manually that I want to perform in the Docker file.

So if I want to install nmap, then I would get into the container and install nmap (as you will see below)

So let’s start the vmware/powerclicore container, and let’s make sure we’re in bash so we can install the things we need to install:

~> docker run -it --entrypoint=/bin/bash vmware/powerclicore
root [ ~ ] yum install nmap
Refreshing metadata for: 'VMware Photon Extras 3.0 (x86_64)'
Refreshing metadata for: 'VMware Photon Linux 3.0 (x86_64) Updates'
Refreshing metadata for: 'VMware Photon Linux 3.0 (x86_64)'
photon                                 2498249   100%
Installing:
libpcap                                                      x86_64                         1.9.1-1.ph3                              photon-updates                       286.45k 293328
pinentry                                                     x86_64                         1.1.0-1.ph3                              photon                               148.06k 151613
npth                                                         x86_64                         1.6-1.ph3                                photon                                 21.01k 21518
libksba                                                      x86_64                         1.3.5-1.ph3                              photon                               285.41k 292257
libassuan                                                    x86_64                         2.5.1-1.ph3                              photon                               114.66k 117409
gnupg                                                        x86_64                         2.2.18-2.ph3                             photon-updates                        9.46M 9918558
pcre                                                         x86_64                         8.44-1.ph3                               photon-updates                       727.52k 744979
nmap                                                         x86_64                         7.91-1.ph3                               photon-updates                      23.16M 24280493

Total installed size:  34.16M 35820155
Is this ok [y/N]: y

Downloading:
libpcap                                 133058   100%
pinentry                                 84100   100%
npth                                     14968   100%
libksba                                 142274   100%
libassuan                                53808   100%
gnupg                                  4233872   100%
pcre                                    304775   100%
nmap                                   6138762   100%
Testing transaction
Running transaction
Installing/Updating: libassuan-2.5.1-1.ph3.x86_64
Installing/Updating: pinentry-1.1.0-1.ph3.x86_64
Installing/Updating: pcre-8.44-1.ph3.x86_64
Installing/Updating: libksba-1.3.5-1.ph3.x86_64
Installing/Updating: npth-1.6-1.ph3.x86_64
Installing/Updating: gnupg-2.2.18-2.ph3.x86_64
Installing/Updating: libpcap-1.9.1-1.ph3.x86_64
Installing/Updating: nmap-7.91-1.ph3.x86_64

Complete!

Awesome – that worked! For giggles and for use later, I am going to install vim too, but I am going to use the -y this time so I am not prompted:

#> yum install vim -y

I also want to have my PowerCLI git repo available https://github.com/bryansullins/myvmwarekit in the container, and while we’re at it, let’s test out the Module Import:

root [ ~ ] which git
/usr/bin/git
#### YEA! git's installed already!
root [ ~ ] git clone https://github.com/bryansullins/myvmwarekit.git
Cloning into 'myvmwarekit'...
remote: Enumerating objects: 130, done.
remote: Counting objects: 100% (130/130), done.
remote: Compressing objects: 100% (91/91), done.
remote: Total 130 (delta 63), reused 95 (delta 33), pack-reused 0
Receiving objects: 100% (130/130), 42.71 KiB | 470.00 KiB/s, done.
Resolving deltas: 100% (63/63), done.
root [ ~ ] cd myvmwarekit/MY.VMWAREKIT/
root [ ~/myvmwarekit/MY.VMWAREKIT ] pwsh
PowerShell 7.1.1
Copyright (c) Microsoft Corporation.

https://aka.ms/powershell
Type 'help' to get help.

   A new PowerShell stable release is available: v7.1.3
   Upgrade now, or check out the release page at:
     https://aka.ms/PowerShell-Release?tag=v7.1.3

PS /root/myvmwarekit/MY.VMWAREKIT>Import-Module ./MY.VMWAREKIT.psd1 -Global -Force -Verbose
VERBOSE: Loading module from path '/root/myvmwarekit/MY.VMWAREKIT/MY.VMWAREKIT.psd1'.
VERBOSE: Loading module from path '/root/myvmwarekit/MY.VMWAREKIT/MY.VMWAREKIT.psm1'.
VERBOSE: Exporting function 'Connect-MYvCenters'.
VERBOSE: Exporting function 'Get-MYFWSettingsSyslog'.
VERBOSE: Exporting function 'Get-MYNFSShareInfo'.
VERBOSE: Exporting function 'Get-MYHostConfiguration'.
VERBOSE: Exporting function 'Get-MYWWNs'.
VERBOSE: Exporting function 'Get-MYSyslogLogHost'.
VERBOSE: Exporting function 'Get-MYVMsandDatastores'.
VERBOSE: Exporting function 'Get-MYPhysicalNICInfo'.
VERBOSE: Exporting function 'Get-MYAllHardwareDriverInfo'.
VERBOSE: Exporting function 'Get-MYBIOSInfo'.
VERBOSE: Exporting function 'Get-MYDSPercentFree'.
VERBOSE: Exporting function 'Get-MYCPURatio'.
VERBOSE: Exporting function 'Get-MYCPUCount'.
VERBOSE: Exporting function 'Get-MYNumberofVMsPerHost'.
VERBOSE: Exporting function 'Get-MYvRAMOvercommit'.
VERBOSE: Exporting function 'Get-MYHostManagementInfo'.
VERBOSE: Exporting function 'Get-MYvSwitchSecPolicy'.
VERBOSE: Exporting function 'Get-MYCiscoDiscoveryInfo'.
VERBOSE: Exporting function 'Get-MYCDandDVD'.
VERBOSE: Exporting function 'Get-MYVIEventPlus'.
VERBOSE: Exporting function 'Move-MYVMOffCurrHost'.
VERBOSE: Exporting function 'Get-MYvMotionHistory'.
VERBOSE: Exporting function 'Set-MYScratch'.
VERBOSE: Exporting function 'Set-MYSyslogValue'.
VERBOSE: Exporting function 'Set-MYSYSLOGGlobalDir'.
VERBOSE: Exporting function 'Set-MYRRDefault'.
VERBOSE: Exporting function 'Set-MYFWAllowSyslog'.
VERBOSE: Exporting function 'Start-MYSSH'.
VERBOSE: Exporting function 'Stop-MYSSH'.
VERBOSE: Exporting function 'Set-MYPowerPolicyHigh'.
VERBOSE: Exporting function 'Set-MYDisconnectCD'.
VERBOSE: Exporting function 'Set-MYDisconnectCDSingleVM'.
VERBOSE: Exporting function 'Set-MYTaggedVMtoPartial'.
VERBOSE: Exporting function 'Set-MYChangeESXiPassword'.
VERBOSE: Exporting function 'Set-MYDisableAdmissionControl'.
VERBOSE: Importing function 'Connect-MYvCenters'.
VERBOSE: Importing function 'Get-MYAllHardwareDriverInfo'.
VERBOSE: Importing function 'Get-MYBIOSInfo'.
VERBOSE: Importing function 'Get-MYCDandDVD'.
VERBOSE: Importing function 'Get-MYCiscoDiscoveryInfo'.
VERBOSE: Importing function 'Get-MYCPUCount'.
VERBOSE: Importing function 'Get-MYCPURatio'.
VERBOSE: Importing function 'Get-MYDSPercentFree'.
VERBOSE: Importing function 'Get-MYFWSettingsSyslog'.
VERBOSE: Importing function 'Get-MYHostConfiguration'.
VERBOSE: Importing function 'Get-MYHostManagementInfo'.
VERBOSE: Importing function 'Get-MYNFSShareInfo'.
VERBOSE: Importing function 'Get-MYNumberofVMsPerHost'.
VERBOSE: Importing function 'Get-MYPhysicalNICInfo'.
VERBOSE: Importing function 'Get-MYSyslogLogHost'.
VERBOSE: Importing function 'Get-MYVIEventPlus'.
VERBOSE: Importing function 'Get-MYvMotionHistory'.
VERBOSE: Importing function 'Get-MYVMsandDatastores'.
VERBOSE: Importing function 'Get-MYvRAMOvercommit'.
VERBOSE: Importing function 'Get-MYvSwitchSecPolicy'.
VERBOSE: Importing function 'Get-MYWWNs'.
VERBOSE: Importing function 'Move-MYVMOffCurrHost'.
VERBOSE: Importing function 'Set-MYChangeESXiPassword'.
VERBOSE: Importing function 'Set-MYDisableAdmissionControl'.
VERBOSE: Importing function 'Set-MYDisconnectCD'.
VERBOSE: Importing function 'Set-MYDisconnectCDSingleVM'.
VERBOSE: Importing function 'Set-MYFWAllowSyslog'.
VERBOSE: Importing function 'Set-MYPowerPolicyHigh'.
VERBOSE: Importing function 'Set-MYRRDefault'.
VERBOSE: Importing function 'Set-MYScratch'.
VERBOSE: Importing function 'Set-MYSYSLOGGlobalDir'.
VERBOSE: Importing function 'Set-MYSyslogValue'.
VERBOSE: Importing function 'Set-MYTaggedVMtoPartial'.
VERBOSE: Importing function 'Start-MYSSH'.
VERBOSE: Importing function 'Stop-MYSSH'.

Neato!

The good news is that it’s working, but the bad news, if you were paying attention in Part 1, is that this all goes away once you exit the container. If you are like . . . not picking up what I am putting down here . . . Let me explain it, in the most plain terms I can:

** clears throat **
. . . .

** picks up a bullhorn megaphone **

. . . .

CONTAINERS ARE EPHEMERAL!

So, let’s take what we have done manually and put it into a Dockerfile. This might take some trial and error, but that’s OK.

Create a file called Dockerfile (case specific) with the following contents:

from vmware/powerclicore

RUN yum install nmap vim -y
RUN git clone https://github.com/bryansullins/myvmwarekit.git

Now type:

#> docker build -t dockerhubreponame/custpowercli:latest .

It’s important to use the dockerhubreponame!

I will talk about the -t option and why later. For now, to build the new image with the customizations in the Dockerfile (docker detects the file automatically since it’s named Dockerfile):

~> docker build -t dockerhubreponame/custpowercli:latest .
[+] Building 124.5s (7/7) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                                 0.0s
 => => transferring dockerfile: 159B                                                                                                                                                                 0.0s
 => [internal] load .dockerignore                                                                                                                                                                    0.0s
 => => transferring context: 2B                                                                                                                                                                      0.0s
 => [internal] load metadata for docker.io/vmware/powerclicore:latest                                                                                                                                1.4s
 => [1/3] FROM docker.io/vmware/powerclicore@sha256:0a10912293e3f848c01947de94a4a79c109468e7d60b9c30bacfebbbbcc79b78                                                                                20.1s
 => => resolve docker.io/vmware/powerclicore@sha256:0a10912293e3f848c01947de94a4a79c109468e7d60b9c30bacfebbbbcc79b78                                                                                 0.0s
 => => sha256:b982b6cdcb4042ee5d6a13f1c38332d693d7ac36668919bb873fefe0c267118f 15.21MB / 15.21MB                                                                                                     1.5s
 => => sha256:a836ba72e6271095276ce976eb0f284ba7810d43ae10f8ddb1e6837b5e70e98e 257.56MB / 257.56MB                                                                                                  11.4s
 => => sha256:0a10912293e3f848c01947de94a4a79c109468e7d60b9c30bacfebbbbcc79b78 742B / 742B                                                                                                           0.0s
 => => sha256:30ce0ad1753e88e046e93e2355e1a13813c44502b1130cb10f362e08def96b08 4.91kB / 4.91kB                                                                                                       0.0s
 => => extracting sha256:b982b6cdcb4042ee5d6a13f1c38332d693d7ac36668919bb873fefe0c267118f                                                                                                            1.2s
 => => extracting sha256:a836ba72e6271095276ce976eb0f284ba7810d43ae10f8ddb1e6837b5e70e98e                                                                                                            8.4s
 => [2/3] RUN yum install nmap vim -y                                                                                                                                                              100.7s
 => [3/3] RUN git clone https://github.com/bryansullins/myvmwarekit.git                                                                                                                              1.2s
 => exporting to image                                                                                                                                                                               0.9s
 => => exporting layers                                                                                                                                                                              0.9s
 => => writing image sha256:ff764b65f98eff43d53887e9443bbb4671f70cd2e6851d702f13966d132107d2                                                                                                         0.0s
 => => naming to docker.io/dockerhubreponame/custpowercli:latest                                                                                                                                          0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

Now let’s see what that created:

~> docker image list
REPOSITORY                  TAG       IMAGE ID       CREATED         SIZE
dockerhubreponame/custpowercli   latest    ff764b65f98e   8 seconds ago   868MB

Now launch your new container! Let’s also see if the git repo made it:

~> docker run -ti dockerhubreponame/custpowercli
PowerShell 7.1.1
Copyright (c) Microsoft Corporation.

https://aka.ms/powershell
Type 'help' to get help.

PS /root> ls -la
total 66708
drwxr-x--- 1 root root     4096 2021-05-20 02:45 .
drwxr-xr-x 1 root root     4096 2021-05-20 03:04 ..
drwxr-xr-x 3 root root     4096 2021-04-16 13:08 .cache
drwxr-xr-x 4 root root     4096 2021-04-16 13:08 .config
drwxr-xr-x 1 root root     4096 2021-04-16 13:08 .local
-rw-r--r-- 1 root root      227 2021-04-16 13:08 .wget-hsts
drwxr-xr-x 7 root root     4096 2019-11-19 21:53 PowerCLI-Example-Scripts
drwxr-xr-x 4 root root     4096 2021-05-20 02:45 myvmwarekit
-rw-r--r-- 1 root root 68266842 2021-01-14 23:12 powershell-7.1.1-linux-x64.tar.gz

Hey! It works!

Pushing the Custom Image Up to Docker Hub

For the last step, let’s say we want this to be a publicly-available image from docker hub. Keep in mind, of course, if you are doing this in prod, people usually setup their own private repos, like docker harbor. I don’t have docker harbor running, so we are going to push this to Docker Hub.

The process is mostly the same, but it will depend on the authentication requirements of your private docker harbor application. You will have to use the docker login command to force docker to use the docker harbor credentials.

If you don’t have a Docker Hub account, go ahead and make one now.

Try typing docker login to see if you are logged into docker hub. If you are not, it will prompt you since by default Docker Desktop will use Docker Hub to host your repo.

Once you have your docker hub account, it’s very simple:

~> docker push dockerhubreponame/custpowercli
Using default tag: latest
The push refers to repository [docker.io/dockerhubreponame/custpowercli]
95bc112b4aa4: Pushed
d47615d71843: Pushed
a413b6da7717: Mounted from vmware/powerclicore
f64c6a3b5be3: Mounted from vmware/powerclicore
latest: digest: sha256:a49fd2018e9862425603c298d9009e6c608fdf301accc115d4b7fbbd497e3724 size: 1164

Now log on in to Docker Hub and see if it made it up there. It certainly did for me:

Custom PoweCLI Image on my Docker Hub

If you want to see something really cool, delete your custom image from your docker image list and then launch it again. docker will then pull your image from docker hub!

Thanks for reading!

Hit me up on twitter @RussianLitGuy or email me at bryansullins@thinkingoutcloud.org. I would love to hear from you.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s