Layer Caching & BuildKit Cache Mounts

Order instructions from least to most frequently changing, and use BuildKit cache mounts so package downloads survive across builds.

The build cache is the single biggest lever on build speed. The rule never changes: put the stuff that rarely changes near the top. Copy dependency manifests and install before you copy your source, so editing one line of code does not re-run npm ci.

The classic ordering

  1. FROM base image.
  2. Copy only package.json / go.mod / requirements.txt.
  3. Install dependencies (this layer caches until the manifest changes).
  4. Copy the rest of the source.
  5. Build.

BuildKit cache mounts

Even with good ordering, a manifest change re-downloads every package from scratch. BuildKit cache mounts fix this: they keep a persistent cache directory (npm cache, apt cache, Go module cache) mounted only during that RUN, so it never bloats a layer but survives across builds.

Add # syntax=docker/dockerfile:1 at the very top to unlock RUN --mount=type=cache. BuildKit is the default builder in modern Docker, so this just works.

Example

Example Β· dockerfile
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app

# Copy only the manifest first for cache-friendly installs
COPY package.json package-lock.json ./

# Persistent npm cache that survives across builds but never enters a layer
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# Source copied last so code edits don't bust the install layer
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"]

When to use it

  • A developer moves COPY . . to the last instruction so a one-line code change does not re-run npm install in CI.
  • A team uses BuildKit cache mounts for pip and apt to avoid re-downloading packages even when requirements.txt changes.
  • A monorepo CI pipeline passes --cache-from to reuse the last pushed image's layers, reducing build time from 8 minutes to 90 seconds.

More examples

Cache-optimised dependency install

Copies only package manifests first so the npm ci layer is cached as long as dependencies do not change.

Example Β· dockerfile
FROM node:20-alpine
WORKDIR /app
# Separate copy of manifests β€” only re-runs npm ci if these change
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# Source changes only bust this later layer
COPY . .

BuildKit cache mount for pip

Uses a BuildKit cache mount so pip's HTTP cache persists between builds without being stored in an image layer.

Example Β· dockerfile
# syntax=docker/dockerfile:1
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt
COPY . .

Use remote cache in CI

Pulls cache layers from the registry before building and writes updated cache back, sharing it across CI runners.

Example Β· bash
docker buildx build \
  --cache-from type=registry,ref=myregistry/myapp:cache \
  --cache-to   type=registry,ref=myregistry/myapp:cache,mode=max \
  -t myregistry/myapp:latest \
  --push .

Discussion

  • Be the first to comment on this lesson.