Reviewing the Dockerfile in Depth

Reviewing the Dockerfile in Depth

Let's take a look at the instructions used in the Dockerfile example. We will look at them in the order in which they appear:


The FROM instruction tells Docker which base you would like to use for your image; as already mentioned, we are using Alpine Linux, so we simply have to put the name of the image and the release tag we wish to use. In our case, to use the latest official Alpine Linux image, we simply need to add alpine:latest.


The LABEL instruction can be used to add extra information to the image. This information can be anything from a version number to a description. It's also recommended that you limit the number of labels you use. A good label structure will help others who have to use our image later on.

However, using too many labels can cause the image to become inefficient as well, so I would recommend using the label schema detailed at You can view the containers' labels with the following Docker inspect command:

$ docker image inspect <IMAGE_ID>

Alternatively, you can use the following to filter just the labels:

$ docker image inspect -f {{.Config.Labels}} <IMAGE_ID>


The RUN instruction is where we interact with our image to install software and run scripts, commands, and other tasks. As you can see from our RUN instruction, we are actually running three commands:

RUN apk add --update nginx && \
        rm -rf /var/cache/apk/* && \
        mkdir -p /tmp/nginx/

The first of our three commands is the equivalent of running the following command if we had a shell on an Alpine Linux host:

$ apk add --update nginx

This command installs nginx using Alpine Linux's package manager.

We are using the && operator to move on to the next command if the previous command was successful. To make it more obvious which commands we are running, we are also using \ so that we can split the command over multiple lines, making it easy to read.

The next command in our chain removes any temporary files and so on to keep the size of our image to a minimum:

$ rm -rf /var/cache/apk/*

The final command in our chain creates a folder with a path of /tmp/nginx/, so that nginx will start correctly when we run the container:

$ mkdir -p /tmp/nginx/

We could have also used the following in our Dockerfile to achieve the same results:

RUN apk add --update nginx
RUN rm -rf /var/cache/apk/*
RUN mkdir -p /tmp/nginx/

However, much like adding multiple labels, this is considered to be considered inefficient as it can add to the overall size of the image, which for the most part we should try to avoid. There are some valid use cases for this, which we will look at later in the chapter. For the most part, this approach to running commands should be avoided when your image is being built.


At first glance, COPY and ADD look like they are doing the same task; however, there are some important differences. The COPY instruction is the more straightforward of the two:

COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/default.conf /etc/nginx/conf.d/default.conf

As you have probably guessed, we are copying two files from the files folder on the host we are building our image on. The first file is nginx.conf.


The EXPOSE instruction lets Docker know that when the image is executed, the port and protocol defined will be exposed at runtime. This instruction does not map the port to the host machine, but instead, opens the port to allow access to the service on the container network.

For example, in our Dockerfile, we are telling Docker to open port 80 every time the image runs:

EXPOSE 80/tcp


ENTRYPOINT specifies the default command to execute when the container is created. CMD provides the default arguments for the ENTRYPOINT instruction.

For example, if you want to have a default command that you want to execute inside a container, you could do something similar to the following example, but be sure to use a command that keeps the container alive. In our case, we are using the following:

ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

What this means is that whenever we launch a container from our image, the nginx binary is executed, as we have defined that as our ENTRYPOINT, and then whatever we have as the CMD is executed, giving us the equivalent of running the following command:

$ nginx -g daemon off;

Another example of how ENTRYPOINT can be used is the following:

$ docker container run --name nginx-version dockerfile-example -v

This would be the equivalent of running the following command on our host:

$ nginx -v

Notice that we didn't have to tell Docker to use nginx. As we have the nginx binary as our entry point, any command we pass overrides the CMD that had been defined in the Dockerfile.

This would display the version of nginx we have installed, and our container would stop, as the nginx binary would only be executed to display the version information and then the process would stop. We will look at this example later in this chapter, once we have built our image.


specifies the username or the UID to use when running the container image for the RUN, CMD, and ENTRYPOINT instructions in the Dockerfile. It is a good practice to define a different user other than root for security reasons.


The WORKDIR instruction sets the working directory for the same set of instructions that the USER instruction can use (RUN, CMD, and ENTRYPOINT). It will allow you to use the CMD and ADD instructions as well.