Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Safe images: small, non-root, no secrets

After this lesson: I can say what makes an image safe to ship and fix an unsafe Dockerfile.

55 min lesson6 commandsDocker basics lab: learn containers, logs and restarts by fixing a broken service

A bug in a small shop's web server

A safe container image is small, pinned to one version, runs as an ordinary user and holds no secrets. Each of these limits what an attacker can do if the program inside is broken.

Somebody finds a bug in the program that serves a shop's pages. They can now make that program do things.

What they can reach next depends on who the program runs as, what else is in the image, and what is written in it.

By the end of this lesson

Note

I can say what makes an image safe to ship and fix an unsafe Dockerfile.

Key words

WordWhat it meansExample
Rootthe user who may change anything on the systemuid=0
Non-root useran ordinary user with limited rights
Pinned tagan image tag that names one exact versionnginx:1.27-alpine
Secreta password or key that only the program should know
Attack surfaceeverything in an image that an attacker could use

The cells students fill in are left blank in this preview.

Run as a user who can do little

Unless told otherwise, a container's program runs as root. Root may change any file the container can write to, including a mounted volume.

A non-root user may not. If the program is attacked, the damage stays smaller. Compare the two main services of your lab.

Who does web run as?

Command
docker exec web id

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Who does cache run as?

Command
docker exec cache id

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Let cache try to write in /etc

Command
docker exec cache touch /etc/probe

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Let web try the same

Command
docker exec web touch /etc/probe

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Making a user in a Dockerfile (our example)

Code: bash
FROM python:3.12-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
RUN adduser -D -u 10001 shop
USER shop
CMD ["python", "app.py"]

A common mistake

Warning

Many people think root inside a container does not matter, because the container is walled off. Walls have bugs, and a mounted volume is real data. Add a user and switch to it with USER, as the lab's cache does.

Less in the image means less to attack

Every program in an image is one more thing that can have a flaw. Every tool in it is one more thing an attacker can use.

A slim base such as an Alpine image carries far less. The tag decides which version you get. A tag such as latest moves, so a rebuild next month may be a different image.

Worked example: two tags of one image

TagSize on our test machineWhat it means
nginx:1.27284 MBa full system underneath
nginx:1.27-alpine76.8 MBa small system underneath
nginx:latestnot measuredwhatever is newest when you build

The cells students fill in are left blank in this preview.

Which tag does cache pin?

Command
docker inspect --format '{{.Config.Image}}' cache

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A secret written into an image stays there

An image is shared. It is pushed to a registry, copied to servers and handed to colleagues. Whatever is in a layer goes with it.

A password in a Dockerfile is in the layers for good, even if a later step deletes the file. Keep secrets out of the image and give them to the container when it starts.

A Dockerfile holding a secret (do not copy)

Code: bash
FROM alpine:3.20
ENV API_KEY=abc123-not-real
RUN echo hello

Worked example: where a secret showed up

How it was setWhere we read it backWhat it printed
ENV in the Dockerfiledocker history --no-truncthe line ENV API_KEY=abc123-not-real
-e when the container starteddocker inspect, Config.EnvDB_PASSWORD=hunter2 in the list
Neithernothing in the image or the settingsa secret handed over at run time instead

The cells students fill in are left blank in this preview.

Read everything cache was told

Command
docker inspect --format '{{json .Config.Env}}' cache

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Going further

Take the Dockerfile with a secret in it. Rewrite it so that it pins its tag, runs as a user and holds no secret.

Then run the three functions of the assignment over the old version and your new one. The new one should come back clean.

About this lab and its source: Docker basics lab: learn containers, logs and restarts by fixing a broken service

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.