Getting Started with Docker – Part 2

In the previous post we may have started running before we could walk. In this post we’ll first take a few steps back to make sure we cover the basics before diving deeper.

Building an image

When we examined the python:3.6.3-alpine3.6 image’s dockerfile, we saw the components which are used to create a Docker image:

  • FROM alpine3.6
  • ENV commands
  • RUN commands
  • A single CMD command

OK, so we already know that the  FROM alpine3.6 command means that the Python image is going to use the  Apline 3.6 as its Linux operating system. ENV, as its name suggests, is used to specify environment variables for the image. That leaves us with RUN and CMD.

If you’ve used Linux, it’s highly likely that the  RUN commands look familiar. Here’s why – as described in this GoinBigData post, when  RUN commands are written in the  <instruction> <command> style (known as shell form), they’re executed using /bin/sh -c. In other words, they’re standard Linux commands/scripts. (If you don’t know the difference between sh and bash, have a read of this StackOverflow question.)

To sum up what we’ve just covered – Docker Engine examines the dockerfile, spins up a Linux OS (Alpine in this instance), runs a bunch of sh  commands and sets a few environment variables. It really is that simple.

OK, now let’s now move on to the final piece of the puzzle – the  CMD command. As described in the GoinBigData post linked to above, the  CMD ["python3"] style is known as “exec form”. Instead of running through /bin/sh -c, exec form runs the executable directly, which in this case is python3.

You’re probably thinking, apart from the way in which they’re executed, what is the difference between  RUN and  CMD commands? Why can we run multiple RUN commands, and only a single CMD command?

Docker Engine will execute  RUN commands during the creation of an image. For example, it will install the Python, create symbolic links, remove unnecessary files, etc, as part of the image creation process. Once done, the image is ready to be used as a container, and this is where the  CMD command comes into play.

Warning: Hold onto your hat, we’re about to go deep again!


In Part 1 of this series I said that “When done correctly, containers only have a single concern (formally referred to as a single process)”. In the case of the Python image we’ve been dissecting, that’s exactly what the CMD takes care of.

Let’s spin up another container:

In the above command, the flags used achieve the following:

  • -d: Makes the container a daemon. This is useful for us in this instance because we don’t want to be dropped into the Python interpreter.
  • --rm: Delete’s the container as soon as it is stopped

Now that we’ve got our container up and running and we’re still on our Docker Host (as opposed to the Python interpreter), let’s do some poking around.

We can see the python3 command was issued after the container was launched. That’s fine, we expected to see that. Let’s now see what the container has to say for itself:

The container is running only running python3 and the ps command we’ve just issued. That’s it. As fascinating as that is, what’s more interesting is the fact that python3 is running as PID 1. I won’t bore you with the details, but in a nutshell it is “a process that must be running at all times, if this process ends, the kernel enters into a panic mode, after which you cannot do anything else, except rebooting” (reference).

In other words, the CMD command tells Docker what it needs to keep track of in order to determine whether a container should stay up or be brought down. This brings us back to why we needed the -it flags when spinning up our Python container. Without them, Docker does the following:

  1. Spins up the container
  2. Launches the  python3 executable
  3. Python sees that it’s got nothing to process so it gracefully terminates
  4. Docker receives the exit 0 exit code from Python and then shuts down the container

We can see this when we spin up mypy2 without the -it or -d flags:

Using the container

Being dropped into the Python interpreter isn’t all that useful, nor is having the container spun up only to be brought down shortly thereafter. Having Docker execute a “hello” message is much more useful, so let’s go ahead and do that now:

Side Note: Notice how in Part 1 we didn’t have to specify the python executable after the image name, but this time we did? That’s because specifying anything after the image name overrides the CMD command in the Dockerfile. I’ll cover this in more detail in Part 3.

What we’ve done with the above command is called a bind mount. Bind mounts allow us to mounted a file or directory on the Docker Host to a file or directory inside of our container. That is to say, the file resides on our Docker Host machine, but we’ve made it available inside of our container too. We’ve then used Python inside of the container to run the bound file.

In the interest of keeping this post to a reasonable length, I’ll leave storage at that for now and will tackle it further in a future post.


Let’s run through what we’ve learned in this post:

  • dockerfiles are used to tell Docker Engine how to create our image
  • For the most part, dockerfiles are made up of sh scripts/commands
  • Images are always based on other images, e.g the Python image we’ve been discussing is based on Alpine Linux
  • We can only have CMD command per dockerfile
  • The CMD command tells Docker which executable to monitor

As always, if you have any questions or have a topic that you would like me to discuss, please feel free to post a comment at the bottom of this blog entry, e-mail at, or drop me a message on Twitter (@OzNetNerd).

Note: This website is my personal blog. The opinions expressed in this blog are my own and not those of my employer.