Working with Dockerfiles

What is a dockerfile? When working with Docker you can download (pull) a pre-built Docker image from a registry such as Docker Hub, or you can build your own images by building from Dockerfiles. This article will look at what Dockerfiles are and how to write them so that you can build your own Docker images.

Creating a Docker image from a docker file is a two step process. First of all we need to create the dockerfile itself, then we need to run the docker build command with the dockerfile to create the docker image. We’ll start by going through what a dockerfile is and how it is created.

What is a Dockerfile?

A dockerfile is a list of instructions in a test file that are used to build the image. These instructions include commands such as setting environment variables, copying files into the image and running commands. A full list of dockerfile instructions can be seen below.

Dockerfile Instructions

  • FROM: Initializes a new build stage and sets the Base Image
  • RUN: Will execute any commands in a new layer
  • CMD: Provides a default for an executing container. There can only be one CMD instruction in a
  • LABEL: Adds metadata to an image
  • EXPOSE: Informs Docker that the container listens on the specified network ports at runtime
  • ENV: Sets the environment variable <key> to the value <value>
  • ADD: Copies new files, directories or remote file URLs from <src> and adds them to the filesystem of the image at the path <dest>.
  • COPY: Copies new files or directories from <src> and adds them to the filesystem of the container at the path <dest>.
  • ENTRYPOINT: Allows for configuring a container that will run as an executable
  • VOLUME: Creates a mount point with the specified name and marks it as holding externally mounted volumes from native host or other containers
  • USER: Sets the user name (or UID) and optionally the user group (or GID) to use when running the image and for any RUNCMD, and ENTRYPOINT instructions that follow it in the Dockerfile
  • WORKDIR: Sets the working directory for any RUNCMDENTRYPOINTCOPY, and ADD instructions that follow it in the Dockerfile
  • ARG: Defines a variable that users can pass at build-time to the builder with the docker build command, using the --build-arg <varname>=<value> flag
  • ONBUILD: Adds a trigger instruction to the image that will be executed at a later time, when the image is used as the base for another build
  • HEALTHCHECK: Tells Docker how to test a container to check that it is still working
  • SHELL: Allows the default shell used for the shell form of commands to be overridden

A Sample Dockerfile

So, how are these instructions used? Let’s have a look at a simple dockerfile example. This is a DockerFile that will create a new image, using Centos as its base image, then installing Python, Selenium and Google Chrome. I’ve included comments in the file to explain what each line does.

# Uses Centos as the base image for the new image
FROM centos

# Creates a new working directory called Scripts then sets this as the working directory
RUN mkdir /scripts
WORKDIR /scripts

# Copies chomedriver, googlechrome.rpm and w3test.py into the image's current working directory
COPY chromedriver .
COPY googlechrome.rpm .
COPY w3test.py .

# Runs some commands to install Python 3 and Selenium, and Google Chrome from the RPM file copied to the image
RUN yum install epel-release -y
RUN yum install python3 -y
RUN pip3 install selenium
RUN yum localinstall googlechrome.rpm -y

# Sets the entrypoint - this command will run every time a container is created from this image. In this case, a python script will run, and return its output. 
ENTRYPOINT python3 w3test.py

The above is a complete Dockerfile – running the docker build command against it will create a working image. But before we look at how to build the image, I wanted to give some further examples of dockerfile instructions. In the above example, I’ve used the FROM, WORKDIR, COPY, RUN and ENTRYPOINT instructions. In the next sections, I’ll go into a bit more detail on each dockerfile instruction with examples of how they are used.

The FROM Instruction

This is always the first instruction in a dockerfile. The FROM instruction starts a new build phase, and sets the base image to be used by the instructions that follow it. In my dockerfile example above, this is:

FROM centos

This sets ‘centos’ as the base image. Another example would be:

FROM ubuntu:latest

This time I’ve also used the ‘latest’ tag to ensure I am using the latest available Ubuntu image. Note that you can have multiple FROM instructions in the same dockerfile, which allows you to split the build into stages or to create multiple images from the same dockerfile.

The RUN Instruction

The RUN instruction is used to run commands in a new layer above the base image. In the example above, I have used the RUN instruction to create a new directory, and then install some software into the image.

RUN yum install python3 -y

The CMD Instruction

The main purpose of the CMD instruction is to provide a default command for a container created from the image. For example, we could have:

CMD whoami

This would run the echo command when a container is created from the image.

Running a container from the image (mytestimage) would look like this:

$ docker run mytestimage
root

The container has ran the ‘whoami’ shell command when the container was executed. Note that the CMD instruction is just a default – it can be overridden when creating a container. For example, if we added the ‘hostname’ command to the docker run command we would get:

$ docker run mytestimage hostname
9c1234c0a5d0

This time, instead of getting the output from the ‘whoami’ command, we have the output from the ‘hostname’ command as the default CMD has been overridden.

The ENTRYPOINT Instruction

The ENTRYPOINT instruction lets you configure the container created from the image run as an executable – it’s used when you want the command specified to run every time a container is created from the image. In my dockerfile above I have used the ENTRYPOINT instruction to ensure my python script runs everytime a container is created:

ENTRYPOINT python3 w3test.py

Whilst it’s possible to override the entrypoint, it’s a little more difficult that overriding CMD instruction – you have to specify the entrypoint as follows:

$ docker run --entrypoint hostname mytestimage

CMD can be used when you want to offer users flexibility in what commands they want to run against the container easily, whereas ENTRYPOINT should be used when you want to container to behave as though it was an executable, as I have in my dockerfile. I’ve packaged the components needed to run the script (python, pip, selenium) and the script itself (w3test.py) into the image, and set the entrypoint as the script file.

CMD and ENTRYPOINT can be a little confusing as they appear to be similar. For further reading on these instructions be sure to check out the dockerfile reference documentation, and also this useful page.

The LABEL Instruction

This instruction is used for adding metadata to the image. A simple example would be to add a version number, which could be done like this:

LABEL version="2.0"

Labels added to an image can be viewed when running the docker image inspect command.

The EXPOSE Instruction

The EXPOSE instruction tells docker which network ports the container will be listening on when it is run. An example of it’s use would be:

EXPOSE 443/tcp

Note that the instruction doesn’t actually publish the port. Instead it acts as documentation to show which ports should be published when a container is created from the image. To actually publish the ports the -P flag should be used with the docker run command to map the exposed ports, or specific port mappings can be specified using the -p flag:

docker run -P mytestimage
docker run -p 443:443/tcp mytestimage

The ENV Instruction

The ENV instruction is used to set an environment variable in the image. This variable may then be used by an application running in the image. The ENV instruction is formatted as a key and a value. For example:

ENV version 1.0

In the example above, ‘version’ is the key, and ‘1.0’ is the value. One typical example of how the ENV instruction is used is to have it update the PATH environment variable of the container, to include the path to an application installed in the container. For example:

ENV PATH /usr/local/nginx/bin:$PATH

The ADD and COPY Instructions

The ADD and COPY instructions are fairly similar. They are both ways of getting files from you host system to be included in the docker container image you are building. I’ve used COPY in my example file above to copy a script into the container image:

COPY w3test.py .

The ADD instruction can also be used to do this. Though ADD also offers some additional functionality. For example, you can use it to auto extract a tar file into the image:

ADD rootfs.tar.xz /.

The VOLUME Instruction

VOLUME is used to create a mount point in a container that connects to the docker host. It can be used to share data between the container and the host, as a means of retaining data, logs, config files and so on.

VOLUME /vol1

When you run a container from the image, a volume should be created, which will be mounted at /vol1 in the container. You can list the docker volumes with:

$ docker volume list

Find out more about how Docker volumes work and what they are used for here.

The USER Instruction

The one is straight forward. The USER instruction sets the username (or UID) of the user to use when running the image, and for any RUN, CMD or ENTRYPOINT commands that follow the USER command in the dockerfile. If the service in the container can run with non-root privileges then you could use USER to switch to using a non-privileged user.

USER myusername

The WORKDIR Instruction

This instruction is used to change the working directory within the image, for example:

WORKDIR /scripts

The ARG Instruction

The ARG instruction lets you specify a variable that can be passed at build-time with the docker build command using the --build-arg <varname>=<value> flag.  You can set default values, which can be overridden when the build is executed. For example:

FROM nginx
ARG buildversion=1

The HEALTHCHECK Instruction

The Healthcheck instruction configures how you tell Docker how to test that a container is still working. A typical example is checking a webserver is still serving pages:

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

The SHELL Instruction

The shell instruction can be used to override the default shell used in the container. On Linux this is [“/bin/sh”, “-c”]. This instruction can be particularly useful on Windows Docker hosts to change the shell to PowerShell rather than CMD.

Building a Docker Image from a DockerFile

The docker build command is used to create a docker image from a dockerfile. At it’s simplest, in the directory containing the docker file, we can run:

docker build -t myimage:latest .

We have passed the docker build command two parameters :

  • -t is the Docker image tag. You can give a name to your image and a tag – I have used myimage:latest
  • The second parameter is ‘.’ This is simply the location for the dockerfile. I am running it from my current directory.

As the command is executed you will see the build steps being performed:

docker build -t myimage:latest .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM centos
latest: Pulling from library/centos
8a29a15cefae: Pull complete
Digest: sha256:fe8d824220415eed5477b63addf40fb06c3b049

Once done you should see a ‘complete!’ message:

Complete!
Removing intermediate container 5a95f416f75e
---> cba6914f8545
Successfully built cba6914f8545
Successfully tagged myimage:latest

We can check the image is now available for use by running the docker image list command:

$ docker image list

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

myimage             latest              cba6914f8545        2 minutes ago       304MB

centos              latest              470671670cac        4 months ago        237MB

We now have a useable image! The docker image inspect command can be used to get more information about the image we have created, whilst the docker image history command will show the layers in the image, and the commands that were ran:

$ docker image history myimage:latest
IMAGE CREATED CREATED BY SIZE COMMENT
cba6914f8545 5 minutes ago /bin/sh -c yum install python3 -y 34.3MB
f17493390298 5 minutes ago /bin/sh -c yum install epel-release -y 33MB
470671670cac 4 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
4 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
4 months ago /bin/sh -c #(nop) ADD file:aa54047c80ba30064… 237MB

Learning Docker?

If you are starting out, then I highly recommend this book. Thirsty for more?

Then it’s time to take your Docker skills to the next level with this book (It’s my favorite). Also, check out my page on Docker Certification

Related posts

Docker Exec Command With Practical Examples

How to Use the Git Stash Command

How to Use the Docker Exec Command

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Read More