Lecture 2 of 1417 minInteractiveFree lecture

Images and layers

A container image is a stack of read-only layers, one per build step. Learn to read and write Dockerfiles, see how the build cache decides what to rebuild, and apply the habits that keep images small, fast and secure.

In this lecture you will

  • Read and write a Dockerfile for a typical application
  • Explain how layers and the build cache work
  • Order Dockerfile instructions to maximize cache reuse
  • Keep images small and secure with slim bases, multi-stage builds and tags

In the last lecture, an image was a “template” for containers. This lecture opens it up. An image is not one big file — it is a stack of layers, and understanding them is the key to fast builds, small images and fewer surprises in production.

A Dockerfile, line by line

Images are built from a recipe called a Dockerfile. Here is a typical one for a Python web service:

# Start from a small official base image
FROM python:3.12-slim
# Working directory inside the image
WORKDIR /app
# Copy only the dependency list, then install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Then copy the application source
COPY . .
# Default command when a container starts
CMD ["python", "app.py"]

Note that Dockerfile comments must be on their own line — a # after an instruction is treated as part of it.

docker build -t shop-web:1.4.2 .

Each instruction that changes the file system — FROM, COPY, RUN — produces a new layer containing just the files it added or changed. WORKDIR and CMD only record settings.

A container sees all the layers merged into one file system. Explore an image built from a slightly careless version of this Dockerfile — one that copied a .env file and then deleted it:

InteractiveInside an imageClick the layers, then switch views
Loading visual…

Deleting a file in a later layer only adds a whiteout marker: the file disappears from the merged view but stays in the earlier layer. Tools such as docker save or a registry download expose every layer.

Layers and the build cache

Layers are content-addressed and cached. When you build again, Docker walks through the instructions and reuses a cached layer as long as nothing it depends on has changed. At the first instruction whose inputs changed — a different file copied, a different command — the cache is broken, and that instruction and every one after it runs again.

That makes the order of instructions decisive. Compare two Dockerfiles below: build once, then simulate editing app.py and rebuild.

InteractiveLayers and the build cacheRebuild after a code change
Loading visual…

Timings are illustrative for a service with a large dependency set.

With dependencies installed first, a code change rebuilds only the final copy step — seconds. Copy the source first, and every code change reinstalls all dependencies — minutes on every build, multiplied across every developer and every CI run.

Keeping images small and safe

Image size affects how fast deployments roll out, how much storage and bandwidth you pay for, and how many vulnerabilities you ship. A few habits go a long way:

Slim base images
Start from minimal variants such as -slim or distroless images instead of full operating-system images.
.dockerignore
Exclude .git, local environments, test data and build output from the build context, so COPY . . stays small and doesn’t leak files.
Multi-stage builds
Compile or bundle in a builder stage with all the tools, then copy only the finished artefact into a small runtime stage.
Pinned versions
Tag images with explicit versions (shop-web:1.4.2) or digests, never just latest, so a deployment is reproducible.
Non-root user
Add a USER instruction so the application doesn’t run as root inside the container.
# Multi-stage build for a Go service
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/api ./cmd/api

FROM gcr.io/distroless/static-debian12
COPY --from=build /out/api /api
USER nonroot
ENTRYPOINT ["/api"]

The final image contains a single binary and almost nothing else — often only a few tens of megabytes.

Pinning matters more than it looks. Here is what can happen to a Deployment that uses latest:

AnimationWhy latest bites
Loading animation…
  1. MondayThe image is built, pushed and tagged latest. The Deployment uses shop-web:latest, and two pods pull it: both run v1.
  2. FridayA new build is pushed with the same tag. latest now points to v2, but nothing in the cluster has changed — yet.
  3. A pod restartsA node restarts a pod or the Deployment scales up. For :latest images Kubernetes pulls on every pod start, so the new pod runs v2 next to the old v1 pods — a release nobody approved.
  4. Pin the versionReference shop-web:1.4.2, or better the digest shop-web@sha256:…. Every pod runs the same bytes, and a rollback means deploying the previous version.
  5. Digests never moveA digest is the SHA-256 hash of the image manifest. It changes if a single byte changes, so it can’t be repointed the way a tag can.

An image is a static artefact. Next, we run containers and connect them — to users, to each other and to storage that outlives them.

Key takeaways

  • Each Dockerfile instruction that changes files creates a layer; layers are cached and shared between images.
  • When a layer changes, every layer after it must be rebuilt — so put rarely changing steps first.
  • Copy dependency manifests and install dependencies before copying the rest of the source.
  • Use small base images, multi-stage builds, a .dockerignore file and pinned versions; don’t bake secrets into images.

Check your understanding

3 questions · answers explained

Q1You change one line of application code. Why does a well-ordered Dockerfile rebuild in seconds?

Docker reuses cached layers until it reaches the first instruction whose inputs changed. If dependencies are installed before the source is copied, that expensive step is reused.

Q2What does a multi-stage build achieve?

Compilers, build tools and caches stay in the builder stage. The final image contains only what is needed at run time — smaller, faster to pull and with fewer vulnerabilities.

Q3Why is using the tag latest in production risky?

A tag is a movable label. Pin a specific version tag — or better, the image digest — so the same deployment always runs the same bytes.