Images & Layers
The previous page established that an image is the filesystem a container sees through its mount namespace. This page is about how that filesystem is built, stored, and shipped — because the design is clever, and understanding it is the difference between fast, reproducible builds and slow, mysterious ones.
The key insight: an image is not one big blob. It’s a stack of read-only layers, each one a set of filesystem changes, glued together by a union filesystem into a single view.
The union filesystem: layers stacked into one view
Section titled “The union filesystem: layers stacked into one view”Imagine sheets of transparency film, each with some files drawn on it. Stack them and look down from above: you see the combined picture, with upper sheets covering whatever’s beneath them. A union (or overlay) filesystem does exactly this with directory trees. Each layer records what changed relative to the layer below — files added, modified, or deleted — and the kernel merges the stack into one coherent filesystem for the container.
┌────────────────────────────┐ ← thin writable layer (per running container) │ layer 4: COPY app code │ read-only │ layer 3: RUN install deps │ read-only │ layer 2: RUN apt-get update │ read-only │ layer 1: FROM base image │ read-only └────────────────────────────┘ merged view = what the container sees as "/"Two consequences fall out of this design, and they matter a lot:
- Layers are shared. If ten images all build
FROM python:3.12-slim, that base layer is stored once on disk and pulled once over the network. Layers are deduplicated across every image that uses them. - The running container’s writes go to a thin writable layer on top. The image layers stay read-only and pristine. Every container started from an image gets its own scratch layer; the image itself never changes. (This is also why container writes are ephemeral — more on that in Networking & Volumes.)
Content addressing: digests, not names
Section titled “Content addressing: digests, not names”Each layer — and the image as a whole — is identified by a cryptographic hash (a SHA-256) of its
contents. This is its digest, and it looks like sha256:3f2a….
Content addressing buys two things. First, deduplication: identical content produces an identical
digest, so the system inherently stores it once. Second, and bigger for production, integrity: a
digest is the content. If you pull sha256:3f2a…, you are guaranteed to get exactly the bytes that
hash produced — not a “newer version,” not a tampered copy, not a typo’d near-match. The digest is a
fingerprint that can’t lie.
Caching: why layer order decides your build speed
Section titled “Caching: why layer order decides your build speed”Here is where this becomes a daily, practical skill. When you build an image, the builder processes instructions top to bottom, producing one layer per instruction (roughly). Before running an instruction, it checks: have I already built this exact layer, from this exact input, on top of this exact parent? If yes, it reuses the cached layer instantly. The rule that makes or breaks you:
This single rule dictates how you order build steps. Put the things that change rarely early (installing system packages, installing dependencies) and the things that change constantly (your own source code) late. Consider the wrong order versus the right order:
# WRONG — code copied before deps are installed.# Every code change busts the (slow) dependency install layer.FROM node:20-slimCOPY . .RUN npm install# RIGHT — copy only the manifest, install, THEN copy code.# A code change reuses the cached install layer entirely.FROM node:20-slimCOPY package.json package-lock.json ./RUN npm install # cached unless package files changeCOPY . . # only this cheap layer rebuilds on a code editSame image, same result — but the second one turns a five-minute rebuild on every commit into a two-second one. We’ll write full Dockerfiles on the next page; the ordering principle is what to carry there.
Small base images: less to ship, less to attack
Section titled “Small base images: less to ship, less to attack”The base image (FROM …) is your bottom layer, and it sets the floor for size and risk. A full
ubuntu base is around 80 MB and ships a whole distro’s worth of tools — most of which your
app never uses, all of which are surface for bugs and vulnerabilities. Slimmer bases exist for a reason:
-slimvariants strip out docs, build tools, and rarely-used packages.alpineimages use a tiny libc (musl) and a minimal userland — often under 10 MB. (Watch for occasional musl-vs-glibc compatibility surprises.)distrolessimages contain only your app and its runtime — no shell, no package manager, almost nothing to exploit.
Smaller images pull faster (cheaper, quicker deploys and autoscaling), and a smaller image has fewer packages, which means fewer CVEs for your scanner to find.
The thread: reproducibility you can verify
Section titled “The thread: reproducibility you can verify”Tie it back. The whole point of Part 3 is killing manual, error-prone steps between build and run. Layers + digests are how that promise is enforced, not just hoped for. Because a layer is content hashed, “the image we tested” and “the image we deployed” can be compared by a single number — match the digest and they are provably the same bytes. There is no “I think it’s the same build”; there’s a fingerprint. And because shared layers are pulled once and cached, this safety is also fast and cheap — you’re not re-downloading or rebuilding the world on every deploy. That is how the layer model makes production safer: it makes “identical everywhere” a checkable fact, and makes checking it free.
The architect’s lens
Section titled “The architect’s lens”The layered, content-addressed image isn’t the only way to package software — answer why it’s the one that won:
- Why does it exist? Because shipping one opaque multi-gigabyte blob per app wastes disk, bandwidth, and build time. Splitting an image into stacked read-only layers merged by a union filesystem lets a base layer shared by ten images be stored and pulled once, and lets the builder cache and reuse layers that didn’t change.
- What problem does it solve? Provable “identical everywhere.” Each layer (and the whole image) is named by a SHA-256 digest, so “the image we tested” and “the image we deployed” are the same bytes if and only if one number matches — turning reproducibility from a hope into a checkable fact, and doing it cheaply because shared layers aren’t re-fetched.
- What are the trade-offs? The cache is a chain, not a set: invalidate one layer and every layer
after it rebuilds, so a sloppy
COPY . .beforenpm installturns a 2-second rebuild into a 5-minute one. Copy-on-write means the first write to a large file copies the whole file up into the writable layer — slow for write-heavy workloads, which belong on a volume. - When should I avoid it? You rarely avoid the model, but avoid fat bases (
ubuntuships a whole distro’s worth of attack surface) when-slim,alpine, ordistrolesswill do — and avoid running databases on the overlay filesystem rather than a volume. - What breaks if I remove it? Drop content addressing and you lose integrity — “is this the build I tested?” becomes a guess instead of a digest match. Drop layer sharing/caching and every deploy re-downloads and rebuilds the world; drop small bases and every image is slower to pull and carries more CVEs for your scanner to find.
Check your understanding
Section titled “Check your understanding”- An image is not one blob. What is it, and what does a union filesystem do with the pieces?
- Why is a base layer shared by ten images stored and pulled only once? Which property makes that automatic?
- Explain the difference between a tag and a digest, and why a digest gives you integrity.
- State the cache invalidation rule, then explain why
COPY package.jsonbeforeCOPY . .makes rebuilds dramatically faster. - Give two distinct reasons (one performance, one security) to prefer a small base image.
Show answers
- An image is a stack of read-only layers, each a set of filesystem changes relative to the one below. A union (overlay) filesystem merges the stack into one coherent view, with upper layers covering whatever they change beneath them.
- Because layers are content-addressed by digest, identical content produces an identical hash, so the system inherently stores and pulls it once and shares it across every image that uses it. Deduplication is automatic from content addressing.
- A tag (
python:3.12) is a mutable, friendly pointer that can be moved to different content tomorrow; a digest (python@sha256:…) is the immutable hash of the content itself. The digest gives integrity because it is the bytes — pull it and you’re guaranteed exactly those bytes or an error. - The rule: once a layer’s cache is invalidated, every layer after it rebuilds too (the cache is a
chain, not a set). Copying
package.jsonand runningnpm installbeforeCOPY . .means a source edit only busts the cheap final copy layer, reusing the cached (slow) dependency install — turning a five-minute rebuild into a two-second one. - Performance: a smaller image pulls faster, so deploys and autoscaling are quicker and cheaper. Security: fewer packages mean a smaller attack surface and fewer CVEs for your scanner to flag.