Container Layers Simulator
A container image is a stack of read-only layers, and each instruction in a Dockerfile
builds one layer that the daemon caches and reuses until something it depends on changes.
Here a six-line Dockerfile turns into six cached layers: bump the code version and rebuild
to see which layers cache-hit instantly and which rebuild. The killer move
is swapping the COPY order so RUN npm install cache-misses on every code change.
The Dockerfile panel lists six editable lines, each with a rough build cost on the
right — npm install is the expensive one at about four seconds. The layer
stack underneath replays the most recent build: green CACHED tags are layers Docker
reused, red MISS tags are layers it had to re-execute, yellow is the one currently
building. The counters at the top track total build time and how many of the six
layers hit the cache.
Hit Rebuild twice. The first build misses everything; the second hits all six and
finishes in a blink. Now click Edit code and Rebuild — only COPY . . and
the line after it rebuild, while npm install above stays cached. Then
click Swap COPY order, rebuild twice to warm the cache, and edit the code again. The
same one-line edit now forces npm install to rerun, four seconds wasted,
even though package.json never changed. One miss invalidates every layer below it, so
a single line's position decides whether a code edit rebuilds in under a second or
pays the full install every time.
Why every line in a Dockerfile is a layer
One layer per instruction, content-addressed by hash.
Every instruction in a Dockerfile produces an image layer — an
immutable, content-addressed filesystem diff over the previous layer.
The layer's hash is computed from its inputs: the instruction text
(RUN npm install), the parent layer's hash, and the
contents of any files brought in by COPY / ADD. Two builds with
identical inputs produce identical layer hashes; Docker can then
reuse the cached layer instead of re-executing the instruction.
The cache works top-down. The build walks layers in order; as long as each layer's inputs match a previously-built layer's hash, the build uses the cache. The first cache miss "breaks" the cache — every layer after it must also rebuild, even if its own inputs haven't changed, because the parent layer is now different. This is why ordering matters: a small change near the top invalidates everything below it.
COPY package.json before COPY . .
Three lines of Dockerfile, ten minutes saved per build.
A naive Dockerfile looks like this:
# BAD — npm install reruns on every code change
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]Every code edit changes the COPY . . input, which invalidates the
cache for that layer, which invalidates every layer after — including
the four-second RUN npm install. Every build pays the
full install cost.
The fix is to copy package.json (and package-lock.json) on its own first, run install against just that, THEN copy the rest of the code:
# GOOD — npm install only reruns when package.json changes
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
CMD ["npm", "start"]Now RUN npm install depends only on package.json's
content. A typical Node project changes package.json maybe once
a week; code changes 50 times a day. With this ordering, 49 of
those 50 daily builds skip the install entirely. On a CI runner,
that's the difference between a 90-second build and a 6-second
build.