5.3.8 · D2MLOps & Deployment

Visual walkthrough — Containerization with Docker

2,270 words10 min readBack to topic

We build on Containerization with Docker and lean on Reproducibility in ML for why we care so much about identical rebuilds.


Step 1 — What "building an image" even means

WHAT. When you run docker build, Docker reads your Dockerfile top to bottom, one instruction at a time. Each instruction produces a new layer — think of it as one thin, frozen sheet of "changes to the filesystem". Stack the sheets and you have the whole image.

WHY start here. Before we can talk about cost we must agree on the unit that costs something. That unit is a layer. No layers, no formula.

PICTURE. Below, each Dockerfile line becomes one pancake in a stack. The bottom pancake (FROM python:3.11-slim) is the base OS + Python; the top pancake (COPY . .) is your code. The whole tower — read from bottom up — is the image.

Figure — Containerization with Docker

Step 2 — Each layer has a build cost

WHAT. Cooking a pancake takes time. pip install -r requirements.txt might take 90 seconds; COPY . . takes a fraction of a second. We give layer number a number — the seconds it takes to build that one layer.

WHY a symbol. We want to add up time. To add things you must first name them. We name the -th layer's build time so that later a sum like means something concrete.

PICTURE. Same tower, now with a number pinned to each pancake. Notice the fat red number: the dependency-install layer is by far the most expensive.

Figure — Containerization with Docker

Step 3 — Every layer carries a fingerprint (a hash)

WHAT. Docker gives each layer a fingerprint — a short string computed from (a) the instruction text and (b) the fingerprint of the layer below it. This fingerprint is called a hash. Same inputs → same hash. Change anything → different hash.

WHY this tool and not just "compare the files"? Docker needs a fast way to ask "is this layer identical to one I built before?" Comparing whole filesystems byte-by-byte would be slow. A hash squashes any input into one short string, so equality is a one-line string compare. That is exactly the question a hash is built to answer.

PICTURE. Each pancake now wears a tag like a1b2…. Crucially, the arrow shows each tag is computed from the tag beneath it plus its own instruction — the fingerprints are chained.

Figure — Containerization with Docker

Step 4 — The cache: reuse a layer if its fingerprint already exists

WHAT. Before cooking a pancake, Docker checks: "do I already have a layer with this exact fingerprint?" If yes, it skips cooking and reuses the frozen one — a cache hit, cost . If no, it must build it — a cache miss, cost .

WHY. This is the entire payoff of hashing. A build where nothing changed is free because every fingerprint matches a stored layer.

PICTURE. Two towers side by side: last build (all green ✓ cache hits) vs a rebuild where nothing changed — every layer is reused, total new work .

Figure — Containerization with Docker

Step 5 — One change poisons everything above it

WHAT. Here is the crux. Suppose you edit app.py. That changes the COPY . . instruction's inputs, so its fingerprint changes. But recall Step 3: every layer's fingerprint is built from the one below it. So the changed fingerprint feeds the next layer up, changing its fingerprint too — and so on to the top.

Let be the index of the first changed layer. Then layer is a miss, and because its hash changed, layers all get new parent-hashes → all misses. Layers below are untouched → all hits (cost ).

WHY. This chaining is not a bug; it guarantees an image is reproducible top-to-bottom. But it means a change ripples upward only, never downward.

PICTURE. The red pancake at position is edited. Watch the red "invalidation" wave travel upward, re-cooking and everything above; everything below stays frozen-green.

Figure — Containerization with Docker

Step 6 — The design lever: make as high as possible

WHAT. You cannot change that the wave goes upward. But you choose the order of your Dockerfile instructions, and that choice sets which layer the daily change lands on — i.e. it sets . Push the thing that changes every commit (your source code) to the top, so and the wave has almost nothing to travel through.

WHY this tool — ordering, not something fancier. The sum shrinks when grows and when the expensive sit below . Both are achieved by one free action: reordering lines. No caching flags, no tricks — just put deps early, code late.

PICTURE. Two Dockerfiles, same instructions, different order. Left (COPY . . early): editing code lands at a low , so the red wave re-cooks the pricey pip layer — slow. Right (requirements.txt first): the same edit lands at the top, red wave is tiny — fast.

Figure — Containerization with Docker

Step 7 — Edge & degenerate cases (never leave the reader stranded)

WHAT. Real builds hit corners. Each below is a special value of .

Case A — nothing changed ( does not exist / is "past the top"). No layer's fingerprint differs, so the sum has no terms: . This is the all-green tower of Step 4. Fully cached rebuild = free.

Case B — the base changed (). You edited FROM python:3.11-slim3.12. The bottom layer changes, so the wave covers everything: — a full rebuild. This is the worst case and it is unavoidable: a new base genuinely invalidates all that sits on it.

Case C — a dependency changed but code did not ( = pip layer). Editing requirements.txt changes the pip layer and everything above it (the code copy). So you pay the 90 s plus the tiny copy on top — correctly, because your installed packages really are different now.

Case D — the "latest" trap (hidden change with ). If your base is python:latest instead of a pinned tag, the base's fingerprint can change without you editing anything, silently forcing tomorrow. This is why Reproducibility in ML and the parent note both scream: pin an immutable tag. Compare with Container Registries, where the same image digest guarantees the same bytes.

PICTURE. Four mini-towers, one per case, colored by how far the red wave climbs: A (none), C (from the middle), B & D (from the floor).

Figure — Containerization with Docker

The one-picture summary

Everything above is one law: the red invalidation wave only climbs, so keep the volatile line at the top and the expensive line at the bottom. The figure compresses the whole derivation — the chained fingerprints, the wave, the sum , and the design lever — into a single frame.

Figure — Containerization with Docker
Recall Feynman retelling — explain the whole walkthrough to a 12-year-old

Imagine building a tower of frozen pancakes, bottom to top, following a recipe card. Each pancake gets a little sticker (a fingerprint) that depends on its own flavour and on the sticker of the pancake underneath it. Next time you build the tower, before cooking any pancake you check: "do I already have one with this exact sticker?" If yes, you just grab the old frozen one — free! If you change one pancake's flavour, its sticker changes, which changes the sticker of the pancake on top, and the one on top of that, all the way up — so you have to re-cook that pancake and everything above it, but nothing below. The cost is just "this pancake plus all its neighbours upstairs," which is exactly , where is the first pancake you changed. The trick: put the pancake you change every single day (your code) at the very top, and the slow, rarely-changed one (installing dependencies) near the bottom. Then your daily change only re-cooks one tiny pancake instead of the whole tower — and your builds go from minutes to a heartbeat.

Recall Quick self-test
  1. Why does changing one layer force all layers above it to rebuild?
  2. What is in , in words?
  3. If nothing changed, what is ?
  4. You change only the base image (FROM). What is , and what's the cost?
  5. Why does latest secretly ruin caching?

Answers: 1) Each layer's fingerprint is built from the one below it, so a changed fingerprint chains upward. 2) The index of the first layer whose fingerprint changed. 3) Zero — every layer is a cache hit. 4) ; cost is the full . 5) Its fingerprint can change with no edit from you, silently forcing (a full rebuild) and breaking reproducibility.


Connections

  • Containerization with Docker — the parent; this page derives its layer-cost formula.
  • CI-CD Pipelines — every commit triggers a rebuild; layer order is the speed knob there.
  • Container Registries — layers are shared by hash, so a good base is pulled once.
  • Reproducibility in ML — immutable tags keep from moving behind your back.
  • Kubernetes Orchestration — pulls the finished image; smaller cached layers = faster rollouts.
Why do layers above a changed layer rebuild?
Each layer's hash is derived from its parent's hash, so a changed fingerprint chains upward, invalidating all layers on top.
In C_rebuild = sum from k to n of c_i, what is k?
The index of the first layer whose fingerprint changed on this build.
Rebuild cost when nothing changed?
Zero — every layer is a cache hit.
Why put COPY . . last?
Code changes every commit; placing it at the top makes k large so only tiny layers rebuild, keeping the expensive pip layer cached.