1.7. Dockerfile

Dockerfile

Until now we have used pre-existing images for running our frontend and backend. Now we will learn how to create are own images.

Docker builds container images by reading instructions from a Dockerfile or, more broadly, a Containerfile. The basic docs on how Dockerfiles work can be found at https://docs.docker.com/engine/reference/builder/ .

Write your first Dockerfile

Let us have a general look at how to build a container image. For that, create a new directory with an empty file named Dockerfile in there.

mkdir myfirstimage
cd myfirstimage
touch Dockerfile

Open Dockerfile in your preferred text editor and add the following instructions:

FROM ubuntu
RUN apt-get update && \
    apt-get install -y figlet && \
    apt-get clean

Build the image

Use the following command to build your image:

docker build -t myfirstimage .
  • -t indicates the name and optionally a tag to apply to the image
  • . indicates the location of the build context (which we will talk more about later, but is basically the directory where our Dockerfile is located)

Please note that the tag can be omitted in most Docker commands and instructions. In that case, the tag defaults to latest. Besides being the default tag there’s nothing special about latest. Despite its name, it does not necessarily identify the latest version of an image.
Depending on the build system it can point to the last image pushed, to the last image built from some branch, or to some old image. It can even not exist at all.
Because of this, you must never use the latest tag in production, always use a specific image version.
Also see: https://medium.com/@mccode/the-misunderstood-docker-tag-latest-af3babfd6375

What happens when we build the image

The output of the Docker build looks similar to this:

[+] Building 13.3s (6/6) FINISHED                                                                                                                                                                     docker:default
 => [internal] load build definition from Dockerfile                                                                                                                                                            0.1s
 => => transferring dockerfile: 127B                                                                                                                                                                            0.0s
 => [internal] load metadata for docker.io/library/ubuntu:latest                                                                                                                                                1.4s
 => [internal] load .dockerignore                                                                                                                                                                               0.1s
 => => transferring context: 2B                                                                                                                                                                                 0.0s
 => [1/2] FROM docker.io/library/ubuntu:latest@sha256:99c35190e22d294cdace2783ac55effc69d32896daaa265f0bbedbcde4fbe3e5                                                                                          3.1s
 => => resolve docker.io/library/ubuntu:latest@sha256:99c35190e22d294cdace2783ac55effc69d32896daaa265f0bbedbcde4fbe3e5                                                                                          0.1s
 => => sha256:99c35190e22d294cdace2783ac55effc69d32896daaa265f0bbedbcde4fbe3e5 6.69kB / 6.69kB                                                                                                                  0.0s
 => => sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4 424B / 424B                                                                                                                      0.0s
 => => sha256:59ab366372d56772eb54e426183435e6b0642152cb449ec7ab52473af8ca6e3f 2.30kB / 2.30kB                                                                                                                  0.0s
 => => sha256:ff65ddf9395be21bfe1f320b7705e539ee44c1053034f801b1a3cbbf2d0f4056 29.75MB / 29.75MB                                                                                                                0.9s
 => => extracting sha256:ff65ddf9395be21bfe1f320b7705e539ee44c1053034f801b1a3cbbf2d0f4056                                                                                                                       1.7s
 => [2/2] RUN apt-get update &&     apt-get install -y figlet &&     apt-get clean                                                                                                                              8.1s
 => exporting to image                                                                                                                                                                                          0.3s 
 => => exporting layers                                                                                                                                                                                         0.2s 
 => => writing image sha256:9739703b2866e9100738cefc2f5c7cb0b9cd2b005e5770829a7608ca55bdcfb5                                                                                                                    0.0s 
 => => naming to docker.io/library/myfirstimage 

First: sending the build context to Docker

transferring context:
2B
...
  • The build context is the . directory given to docker build
  • It is sent (as an archive) to the Docker daemon by the Docker client
  • This allows you to use a remote machine to build using local files
  • Be careful (or patient) if that directory is big and your connection is slow

Second: building step execution

...
[1/2] FROM docker.io/library/ubuntu:latest
 => => resolve docker.io/library/ubuntu:latest
 => => sha256:99c35190e22d294cdace2783ac55effc69d32896daaa265f0bbedbcde4fbe3e5 6.69kB / 6.69kB
 [...]
 => => extracting sha256:ff65ddf9395be21bfe1f320b7705e539ee44c1053034f801b1a3cbbf2d0f4056  
[2/2] RUN apt-get update &&     apt-get install -y figlet &&     apt-get clean                                                                                                                              8.1s
 => exporting to image                                                                                                                                                                                          0.3s 
 => => exporting layers                                                                                                                                                                                         0.2s 
 => => writing image sha256:9739703b2866e9100738cefc2f5c7cb0b9cd2b005e5770829a7608ca55bdcfb5                                                                                                                    0.0s 
 => => naming to docker.io/library/myfirstimage 

This output shows the stages involved in building an image from a Dockerfile. Here’s a breakdown of each step:

  • [internal] load build definition from Dockerfile (0.1s): Docker reads the Dockerfile and loads its contents.

  • [internal] load metadata for docker.io/library/ubuntu:latest (1.4s): Docker fetches metadata for the ubuntu:latest image from Docker Hub to check if it’s up-to-date.

  • [internal] load .dockerignore (0.1s): Docker loads .dockerignore to exclude specific files from the build context.

  • [1/2] FROM docker.io/library/ubuntu:latest (3.1s): Docker starts downloading the ubuntu:latest image with a specific hash sha256:99c35190....

    • sha256 entries represent different image layers:
      • For example, ff65ddf... is a 29.75 MB layer, which Docker downloads and extracts.
  • [2/2] RUN apt-get update && apt-get install -y figlet && apt-get clean (8.1s): Docker executes the RUN command to:

    • Update the package list (apt-get update),
    • Install the figlet package, and
    • Clean up cached package files (apt-get clean) to reduce image size.
  • exporting to image (0.3s): Docker finalizes the build by:

    • Exporting the layers,
    • Writing the image with identifier sha256:9739703..., and
    • Naming the image myfirstimage in the Docker library.

The entire build process took 13.3 seconds, with most time spent downloading layers and installing packages.

The caching system

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

  • After each build step, Docker takes a snapshot
  • Before executing a step, Docker checks if it has already built the same sequence
  • Docker uses the exact strings defined in your Dockerfile:
    • 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
  • All steps after a modified step are re-executed since the filesystem it’s based on may have changed

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

Run it

Now run your image

docker run -ti myfirstimage

You’ll find yourself inside a Bash shell in the container, execute

figlet hello

and you will see the following output:

root@00f0766080ed:/# figlet hello
 _          _ _
| |__   ___| | | ___
| '_ \ / _ \ | |/ _ \
| | | |  __/ | | (_) |
|_| |_|\___|_|_|\___/

root@00f0766080ed:/#

exit the container by executing:

exit

The CMD instruction in Dockerfile

With the CMD instruction in the Dockerfile, we can define the command that is executed when a container is started.

🤔 Can you find out which CMD instruction the ubuntu image uses?

You did find yourself in a shell, so the instruction must either be /usr/bin/bash or /usr/bin/sh.

To find out the CMD instruction of the official Ubuntu image, run:

docker inspect myfirstimage:latest | grep -A2 Cmd

You will see the command defined for the ubuntu image. You can also check the Docker Hub page for Ubuntu to see details on what’s included in the image.

Modify the previously created Dockerfile as follows:

FROM ubuntu
RUN apt-get update && \
    apt-get install -y figlet && \
    apt-get clean

CMD ["figlet", "hello"]

Build the image with:

docker build -t myfirstimagecmd .

And run it:

docker run -ti myfirstimagecmd

It directly executes the defined command and prints out

 _          _ _
| |__   ___| | | ___
| '_ \ / _ \ | |/ _ \
| | | |  __/ | | (_) |
|_| |_|\___|_|_|\___/

Check out https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact for more information.

Frontend app image build

Now we are familiar with the image building process we have a more detailed look at our frontend image. You can find the source code here .

We see that the developer has become quite lazy and has not updated the python to the latest version, he did not even care to create sensible tags. So let us do it ourselves using the tag v1.0

Check out the repository

cd /home/project
git clone https://github.com/songlaa/container-lab-fronted
cd container-lab-fronted

You should have the necessary knowledge now to update and rebuild the image locally. Delete the currently running container and start a new one with updated python.

😳 I'm lost, show me the solution

First of all, we need to check for the latest python base image. You could do this in dockerhub:

grep FROM Dockerfile

We see that currently, we use version 3.9 of python, a look at https://hub.docker.com/_/python shows us that that the most recent version, at the time of writing is 3.13.

Replace the from line with this new value

FROM python:3.13-slim

And then we build it using tag v1.0

docker build -t container-lab-frontend:v1.0 .
docker images

Finally, we kill the currently running container and start our new one, hopefully we still have $ip saved in our shell:

docker stop frontend
docker rm frontend
docker run -d --name frontend -e username=peter -e password=venkman -e servername=$ip container-lab-frontend:v1.0
🤔 What did we update by rebuilding the image?

We did not only update python to a recent version but also the modules in python! Generally, you should build & deploy often to avoid configuration drift and keep your software up to date! A common solution to update your dependencies is https://docs.renovatebot.com/