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:
- 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.
- 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.
- 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:
- 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!
- 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
/custpowercli:latest .dockerhubreponame
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:

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.