Skip to main content

Build Cache

Docker's mechanism for reusing unchanged image layers from previous builds to speed up rebuild time. Each instruction is a cache key — if the instruction and its inputs are unchanged, Docker reuses the cached layer instead of re-executing the instruction.

Build Cache — Why Some Builds Take 45 Seconds and Some Take 8 Minutes

What Is Build Cache in Simple Terms?

Every time Docker builds an image it checks: did this instruction change since last time? If not, it reuses the result from the last build instead of running the instruction again. This is the build cache.

The cache is why your second docker build is much faster than the first. But it is also why moving one COPY instruction in your Dockerfile can turn a 45-second build into an 8-minute build.

Bash
First build: Second build (nothing changed):
Layer 1: FROM 3s Layer 1: FROM -> cache HIT 0.1s
Layer 2: COPY 1s Layer 2: COPY -> cache HIT 0.1s
Layer 3: RUN 240s Layer 3: RUN -> cache HIT 0.1s
Layer 4: COPY 2s Layer 4: COPY -> cache HIT 0.1s
Total: 246s Total: 0.4s
Second build (source code changed):
Layer 1: FROM -> cache HIT 0.1s
Layer 2: COPY package.json -> cache HIT 0.1s
Layer 3: RUN npm install -> cache HIT 0.1s <- still cached!
Layer 4: COPY src/ -> cache MISS 2s <- source changed
Total: 2.3s (not 246s because npm install is still cached)

Cache Invalidation Rules

Bash
Rule 1: If the instruction text changes, cache is busted
"RUN npm install" -> "RUN npm install --verbose" = cache bust
Rule 2: For COPY/ADD, if any copied file changes, cache is busted
COPY package.json ./ -> package.json changed = cache bust
COPY . . -> ANY file changed = cache bust
Rule 3: Once a layer misses, ALL layers below it also miss
Layer 3 busts -> Layers 4, 5, 6 rebuild regardless

Correct Layer Ordering for Maximum Cache Reuse

Dockerfile
# BAD — npm install cache busted on every source change
FROM node:20-alpine
WORKDIR /app
COPY . . # copies everything including source
RUN npm install # cache busted whenever ANY file changes
RUN npm run build
CMD ["node", "dist/server.js"]
# GOOD — npm install cached unless package.json changes
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./ # only dependency manifests
RUN npm install # cached until package.json changes
COPY . . # source comes AFTER install
RUN npm run build
CMD ["node", "dist/server.js"]
# Rule: copy things that change LESS OFTEN first
# copy things that change MOST OFTEN last

Inspecting Cache During Builds

Bash
# See exactly which layers hit or miss cache
docker build --progress=plain .
# Output:
# step 3/8 : COPY package.json package-lock.json ./
# ---> Using cache <- HIT
# step 4/8 : RUN npm install
# ---> Using cache <- HIT (5min saved)
# step 5/8 : COPY . .
# ---> a84f9c2b1d3e <- MISS (source changed)
# step 6/8 : RUN npm run build
# ---> Running in b72c8a9f4e1d <- MISS (cascade)
# Force a full rebuild ignoring all cache
docker build --no-cache .
# Useful when:
# - apt packages need updating
# - debugging cache-related issues
# - verifying build works from scratch

BuildKit Cache Mounts

BuildKit cache mounts keep package manager caches between builds without storing them in image layers:

Dockerfile
# syntax=docker/dockerfile:1
# npm cache reused between builds, NOT in final image
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
# pip cache reused between builds
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# apt cache reused between builds
RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && apt-get install -y curl

Registry Cache for CI/CD

YAML
# GitHub Actions — cache layers in registry between CI runs
- name: Build with registry cache
uses: docker/build-push-action@v5
with:
cache-from: type=gha # read from GitHub Actions cache
cache-to: type=gha,mode=max # write new layers to cache
Tip

Run docker build --progress=plain . on your next build to see every layer and whether it hit or missed cache. This single command tells you exactly which instruction is causing slow builds and which part of your Dockerfile ordering needs fixing.

Common Mistake

Putting COPY . . before RUN npm install. This means every single source file change — even a comment in a README — invalidates the npm install cache. Move package.json copy before the install, source code copy after.

Frequently Asked Questions

How does Docker decide whether a layer can be reused from cache?

Each Dockerfile instruction is checked against the cache using the instruction text itself plus, for `COPY`/`ADD`, a checksum of the files being copied — if either changes, that layer and every layer after it in the Dockerfile rebuilds from scratch, even if those later instructions didn't actually change. This is why instruction order matters: putting a frequently-changing `COPY . .` before a `RUN npm install` invalidates the dependency-install cache on every single code change, forcing a full reinstall each build.

What's the standard best practice for maximizing build cache hits in a Dockerfile?

Copy dependency manifest files (`package.json`, `requirements.txt`) and run the install step before copying the rest of the application source. That way, code changes that don't touch dependencies still hit the cached install layer, and only the final `COPY . .` and subsequent steps rebuild. A common mistake is copying the whole repo in one `COPY . .` early in the Dockerfile, which busts the cache on every commit regardless of whether dependencies actually changed, turning multi-minute installs into a tax on every build.