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.

total
0.0s
hits
0/6

Dockerfile
1 ~1.0s
2 ~0.2s
3 ~0.3s
4 ~4.0s
5 ~0.5s
6 ~0.1s
Layer stack (build result)
layer 1 FROM node:20-alpine
queued
layer 2 WORKDIR /app
queued
layer 3 COPY package.json ./
queued
layer 4 RUN npm install
queued
layer 5 COPY . .
queued
layer 6 CMD ["npm", "start"]
queued
What you're looking at

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.

The general principle. Order layers by change frequency, least-frequent first. Base image (yearly) → system packages (monthly) → app dependencies (weekly) → app code (daily) → entrypoint config (rarely). Each layer should depend only on things that change less often than itself.
Found this useful?