Where to start First steps track · 15 stages
Linux Labs

Dockerfile — an image recipe and docker build

14 min Docker 29.1 · Ubuntu 24.04

A Dockerfile is an image recipe: base, files, build and start commands; docker build turns it into an image.

Cheat sheet

6 commands Printable version
  • sudo docker build -t app:1.1 . build from ./Dockerfile
  • sudo docker build -t app:1.1 -f Dockerfile.dev . another file
  • sudo docker build --no-cache -t app:1.1 . everything, no cache
  • sudo docker history app:1.1 image layers
  • sudo docker image inspect -f '{{json .Config.Cmd}}' app:1.1
  • sudo docker run --rm app:1.1 ls /www what got into the image

Overview

A Dockerfile is a text file that docker build turns into an image. Every line is an instruction: take a base, copy files, run a command, set the start command. Instructions run top to bottom, and each one that changes files produces a layer.

text
student@ubuntu:~/app$ sudo docker build -t app:1.1 .
#0 building with "default" instance using docker driver
…
#4 [1/4] FROM docker.io/library/busybox:latest@sha256:27be924c62a0…
#4 DONE 0.1s
#5 [internal] load build context
#5 transferring context: 31B done
#6 [2/4] WORKDIR /www
#6 DONE 0.1s
#7 [3/4] COPY index.html .
#7 DONE 0.1s
…
#9 naming to docker.io/library/app:1.1 done

-t app:1.1 gives the image a name and a tag; the dot at the end is the directory to take files from. The build lines are numbered #1, #2…: first the service steps [internal], then the instructions in order — [2/4] means the second of four. The last line of a step tells how it ended: DONE with the time, or CACHED. CMD, EXPOSE and ENV get no steps of their own: they record image settings.

On the lab machine BuildKit runs the build (the buildx component, the docker-buildx package).

Instructions

Instruction What it does Layer with files
FROM busybox the base image to build on the base's layers
WORKDIR /www working directory from here on and at run time; creates it yes
COPY index.html . a file from the build context into the image yes
RUN adduser -D web run a command at build time yes
ENV MODE=prod an environment variable at build and run time no
EXPOSE 80 a note "the service listens on 80"; publishes nothing no
USER web who runs the RUNs below and the main process no
VOLUME /data a directory for a volume: without -v it gets a new anonymous volume no
CMD ["httpd", "-f"] the default start command no
ENTRYPOINT ["echo"] the fixed part of the start command no

FROM comes first. Docker looks for the base among the images on the machine and goes to a registry if it is missing. The lab machine has no network, so FROM can only use loaded images: alpine, nginx:alpine, busybox.

RUN runs at build time, CMD every time a container starts. RUN apk add … fails without network: that command fetches packages from the internet.

EXPOSE opens nothing: docker run -p publishes a host port (Ports). An ENV from the image can be overridden at run time: docker run -e MODE=dev. VOLUME /data in the image means every docker run without -v mounts a new empty volume at /data — see Volumes.

Build context

The build context is the directory docker build sends to the Docker service in full: the last argument, usually a dot. The [internal] load build context step says how much was sent: transferring context: 31B done.

COPY takes files only from the context. A COPY path starts at its root and cannot go above it: COPY ../secret.txt . looks for /secret.txt inside the context and fails:

text
ERROR: failed to build: failed to solve: failed to compute cache key:
failed to calculate checksum of ref …: "/secret.txt": not found

If the file you need is outside, copy it into the build directory or build from the directory above: docker build -f app/Dockerfile ..

.dockerignore next to the Dockerfile keeps things out of the context — one pattern per line:

text
*.log
.env
.git

That keeps logs and secrets out of the image and stops the build from shipping gigabytes when the directory holds data.

Build cache

A rebuild with no changes redoes nothing: every step comes from the build cache.

text
#6 [2/4] WORKDIR /www
#6 CACHED
#7 [3/4] COPY index.html .
#7 CACHED

A step comes from the cache if the instruction, the layer below it and — for COPY — the content of the copied files are unchanged. Change one step and it and every step below are rebuilt. After editing index.html only WORKDIR comes from the cache; COPY and the steps below it run again.

Hence the ordering rule: what rarely changes (base, user, dependency install) goes up; application files you edit often go down. --no-cache rebuilds everything.

The cache compares RUN by the command text only. RUN date > /built keeps the date of the first build in the cache.

CMD and ENTRYPOINT

CMD comes in two forms:

Form Example What runs
exec CMD ["httpd", "-f", "-p", "80"] the program itself, PID 1
shell CMD httpd -f -p 80 /bin/sh -c "httpd -f -p 80"

In exec form every word is a separate quoted string. One string for the whole command — CMD ["httpd -f -p 80"] — means "run the program named httpd -f -p 80". The build passes, the container does not start: exec: "httpd -f -p 80": executable file not found in $PATH.

In shell form the main process is a shell, and variables in the line are expanded. Busybox sh with a single command replaces itself with it: docker top shows only httpd.

Words after the image name in docker run replace CMD entirely: docker run --rm app:1.1 ls /www. If the image has an ENTRYPOINT, those words are appended to it:

text
student@ubuntu:~$ cat Dockerfile.ep
FROM busybox
ENTRYPOINT ["echo", "hello"]
CMD ["world"]
student@ubuntu:~$ sudo docker run --rm ep:1
hello world
student@ubuntu:~$ sudo docker run --rm ep:1 student
hello student

The main process must run in the foreground: the container lives as long as it does (Main process). httpd without -f goes to the background, and the container exits at once with Exited (0).

Recipes

4

A site on nginx:alpine

dockerfile
FROM nginx:alpine
COPY site/ /usr/share/nginx/html/

CMD is inherited from the base: nginx is already set to run in the foreground. It serves files from /usr/share/nginx/html.

A process that is not root

dockerfile
FROM busybox
RUN adduser -D -u 1001 web
USER web
ENV GREETING=hi
CMD ["sh", "-c", "id; echo $GREETING"]
text
student@ubuntu:~$ sudo docker run --rm user:1
uid=1001(web) gid=1001(web) groups=1001(web)
hi
student@ubuntu:~$ sudo docker run --rm -e GREETING=bye user:1
uid=1001(web) gid=1001(web) groups=1001(web)
bye

adduser -D is the busybox version: a user with no password. Files from COPY belong to root wherever USER stands: the process can read them but not write to them. To hand files to the user — COPY --chown=web:web data/ /data/.

Check the image before running the service

bash
sudo docker run --rm app:1.1 ls -l /www          # files in place?
sudo docker image inspect -f '{{json .Config.Cmd}}' app:1.1
sudo docker history app:1.1                      # steps and sizes

Rebuild and swap the container

bash
sudo docker build -t app:1.2 .
sudo docker rm -f app
sudo docker run -d --name app -p 8080:80 app:1.2

A new image does not reach a running container by itself — the container has to be re-created.

Troubleshooting

8

DEPRECATED: The legacy builder is deprecated

The old builder runs the build: there is no buildx component (the docker-buildx package), or the DOCKER_BUILDKIT=0 variable turns it off. It is a warning; the build goes on. The error, if any, is in the last lines.

failed to resolve reference at the FROM step

The FROM image is not on the machine, so Docker went to the registry. Check the name and tag against docker images: FROM busybox means busybox:latest. Without network, load the base from a file: docker load -i /opt/images/busybox.tar.

COPY with a path above the context

The COPY path goes above the context: ../file. BuildKit counts it from the context root and answers "/file": not found; the old builder says COPY failed: forbidden path outside the build context. Put the file into the build directory — see Build context.

failed to compute cache key: not found

text
ERROR: failed to build: failed to solve: failed to compute cache key:
failed to calculate checksum of ref …: "/nosuch.html": not found

The old builder says COPY failed: file not found in build context.

The file is not in the context: a typo in the name, the build started from the wrong directory, or .dockerignore excludes the file. Check ls in the directory given as the last docker build argument.

failed to read dockerfile

failed to read dockerfile: open Dockerfile: no such file or directory (the old builder says unable to prepare context) — the context directory has no Dockerfile. Go to the application directory or name the file with -f. path "…" not found means the context directory itself is missing.

executable file not found in $PATH

text
docker: Error response from daemon: … exec: "httpd -f -p 80":
executable file not found in $PATH

The image built and the container was created but did not start (status Created): the image has no program by that name. Most often it is an exec-form CMD written as one string — split the words: ["httpd", "-f", "-p", "80"]. Or the base image lacks the program: busybox has no bash and no python.

A container from the new image exits at once with 0

The start command finished or went to the background. Check docker image inspect -f '{{json .Config.Cmd}}': a service must run in the foreground (httpd -f, nginx -g 'daemon off;').

The change did not reach the container

Check three things in order. Was the image rebuilt after the edit? Was the container created from the new image (docker inspect -f '{{.Image}}' against the image ID)? Is the edited file the one COPY copies, and not in .dockerignore? The last one shows in the COPY step of the build output: CACHED after an edit means a different file is being copied.

Danger zone

2

Secrets in layers

COPY . . without .dockerignore carries .env, keys and .git into the image. A later RUN rm .env does not help: the file stays in the COPY layer, and anyone with the image can pull it out. Exclude secrets in .dockerignore and pass them at run time: -e, --env-file.

FROM without a tag

FROM nginx means nginx:latest, whatever image sits under that name on build day. Two builds of one Dockerfile give different images. Pin a version: FROM nginx:1.27-alpine.

Terms

2
build context
The directory docker build sends to the daemon: the dot in "docker build -t app .". COPY takes files only from it; .dockerignore leaves out the rest.
build cache
Layers from earlier builds that docker build reuses when an instruction and its files are unchanged (Using cache). A changed step is rebuilt along with every step below it.

See also

  • container-images — layers, tags, digests and what docker load does.
  • docker — running a container from the image: ports, volumes, restarts.
  • docker-compose — building and running several services from one file.
  • env-vars — environment variables and ENV.
  • users-groups — users and UIDs for USER.
  • exit-codes — what codes 0, 1, 127 and 137 mean.
Loading…