Building Docker images with a Dockerfile

Objectives

We will build a container image automatically, with a Dockerfile.

At the end of this lesson, you will be able to:

  • Write a Dockerfile.

  • Build an image from a Dockerfile.

Dockerfile overview

  • A Dockerfile is a build recipe for a Docker image.

  • It contains a series of instructions telling Docker how an image is constructed.

  • The docker build command builds an image from a Dockerfile.

Writing our first Dockerfile

Our Dockerfile must be in a new, empty directory.

  1. Create a directory to hold our Dockerfile.

    • $ mkdir myimage

  2. Create a Dockerfile inside this directory.

    • $ cd myimage

    • $ vim Dockerfile

Of course, you can use any other editor of your choice.

Type this into our Dockerfile…

FROM ubuntu
RUN apt-get update
RUN apt-get install figlet
  • FROM indicates the base image for our build.

  • Each RUN line will be executed by Docker during the build.

  • Our RUN commands must be non-interactive. (No input can be provided to Docker during the build.)

In many cases, we will add the -y flag to apt-get.

Build it!

Save our file, then execute:

$ docker build -t figlet .

  • -t indicates the tag to apply to the image.

  • . indicates the location of the build context.

We will talk more about the build context later.

To keep things simple for now: this is the directory where our Dockerfile is located.

What happens when we build the image ?

The output of docker build looks like this:

# docker build -t figlet .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM ubuntu
latest: Pulling from library/ubuntu
a48c500ed24e: Already exists
1e1de00ff7e1: Already exists
0330ca45a200: Already exists
471db38bcfbf: Already exists
0b4aba487617: Already exists
Digest: sha256:c8c275751219dadad8fa56b3ac41ca6cb22219ff117ca98fe82b42f24e1ba64e
Status: Downloaded newer image for ubuntu:latest
 ---> 452a96d81c30
Step 2/3 : RUN apt-get update
 ---> Running in 81dab184c747
Get:1 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
Get:2 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [83.2 kB]
Get:3 http://archive.ubuntu.com/ubuntu bionic-backports InRelease [65.5 kB]
Get:4 http://archive.ubuntu.com/ubuntu bionic/universe Sources [11.5 MB]
Get:5 http://security.ubuntu.com/ubuntu bionic-security InRelease [83.2 kB]
Get:6 http://security.ubuntu.com/ubuntu bionic-security/universe Sources [3786 B]
Get:7 http://security.ubuntu.com/ubuntu bionic-security/universe amd64 Packages [18.8 kB]
Get:8 http://security.ubuntu.com/ubuntu bionic-security/main amd64 Packages [88.6 kB]
Get:9 http://security.ubuntu.com/ubuntu bionic-security/multiverse amd64 Packages [1066 B]
Get:10 http://archive.ubuntu.com/ubuntu bionic/main amd64 Packages [1344 kB]
Get:11 http://archive.ubuntu.com/ubuntu bionic/multiverse amd64 Packages [186 kB]
Get:12 http://archive.ubuntu.com/ubuntu bionic/restricted amd64 Packages [13.5 kB]
Get:13 http://archive.ubuntu.com/ubuntu bionic/universe amd64 Packages [11.3 MB]
Get:14 http://archive.ubuntu.com/ubuntu bionic-updates/universe Sources [28.7 kB]
Get:15 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 Packages [127 kB]
Get:16 http://archive.ubuntu.com/ubuntu bionic-updates/multiverse amd64 Packages [1660 B]
Get:17 http://archive.ubuntu.com/ubuntu bionic-updates/universe amd64 Packages [79.3 kB]
Fetched 25.2 MB in 17s (1496 kB/s)
Reading package lists...
Removing intermediate container 81dab184c747
 ---> 01e04143b340
Step 3/3 : RUN apt-get install figlet
 ---> Running in 2dea10299bd1
Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
  figlet
0 upgraded, 1 newly installed, 0 to remove and 11 not upgraded.
Need to get 133 kB of archives.
After this operation, 752 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu bionic/universe amd64 figlet amd64 2.2.5-3 [133 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 133 kB in 22s (6148 B/s)
Selecting previously unselected package figlet.
(Reading database ... 4035 files and directories currently installed.)
Preparing to unpack .../figlet_2.2.5-3_amd64.deb ...
Unpacking figlet (2.2.5-3) ...
Setting up figlet (2.2.5-3) ...
update-alternatives: using /usr/bin/figlet-figlet to provide /usr/bin/figlet (figlet) in auto mode
update-alternatives: warning: skip creation of /usr/share/man/man6/figlet.6.gz because associated file /usr/share/man/man6/figlet-figlet.6.gz (of link group figlet) doesn't exist
Removing intermediate container 2dea10299bd1
 ---> e8fd21b0252b
Successfully built e8fd21b0252b
Successfully tagged figlet:latest

Sending the build context to Docker

Sending build context to Docker daemon 2.048 kB

  • The build context is the . directory given to docker build

  • It is sent (as an archive) by the Docker client to the Docker daemon.

  • This allows to use a remote machine to build using local files.

  • Be careful (or patient) if that directory is big and your link is slow.

The caching system

If you run the same build again, it will be instantaneous. Why ?

After each build step, Docker takes a snapshot of the resulting image.

Before executing a step, Docker checks if it has already built the same sequence.

Docker uses the exact strings defined in your Dockerfile, so:

  • RUN apt-get install figlet cowsay is different from RUN apt-get install cowsay figlet

  • RUN apt-get update is not re-executed when the mirrors are updated

You can force a rebuild with docker build –no-cache ….

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
figlet              latest              e8fd21b0252b        10 minutes ago      121MB
ubuntu              latest              452a96d81c30        4 weeks ago         79.6MB

Running the image

The resulting image is not different from the one produced manually.

Using image and viewing history

The history command lists all the layers composing an image.

For each layer, it shows its creation time, size, and creation command.

When an image was built with a Dockerfile, each layer corresponds to a line of the Dockerfile.

Introducing JSON syntax

Most Dockerfile arguments can be passed in two forms:

  • plain string: RUN apt-get install figlet

  • JSON list: RUN [“apt-get”, “install”, “figlet”]

We are going to change our Dockerfile to see how it affects the resulting image.

JSON syntax vs string syntax

Compare the new history:

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
ba8d944adee0        39 seconds ago      apt-get install figlet                          992kB
01e04143b340        18 minutes ago      /bin/sh -c apt-get update                       40.5MB
452a96d81c30        4 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           4 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           4 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$…   2.76kB
<missing>           4 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0B
<missing>           4 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:81813d6023adb66b8…   79.6MB
  • JSON syntax specifies an exact command to execute.

  • String syntax specifies a command to be wrapped within /bin/sh -c “…”.

When to use JSON syntax and string syntax

String syntax

  • is easier to write

  • interpolates environment variables and other shell expressions

  • creates an extra process (/bin/sh -c …) to parse the string

  • requires /bin/sh to exist in the container

JSON syntax

  • is harder to write (and read!)

  • passes all arguments without extra processing

  • doesn’t create an extra process

  • doesn’t require /bin/sh to exist in the container