Ch. 13 · Docker

Docker BuildKit and Cache Mounts

Speed up image builds with BuildKit cache mounts, multi-stage builds and dependency-first layer ordering.

~2 min readintermediateupdated Oct 5, 2026

BuildKit is Docker’s modern build engine. It adds cache mounts that keep downloaded dependencies between builds, better parallel scheduling, and RUN --mount syntax that avoids baking caches into image layers. Used with dependency-first ordering, it turns slow rebuilds into fast ones.

Before you start

You should be comfortable with Dockerfiles and layers. This article assumes BuildKit is enabled, which is the default in current Docker.

Step-by-step walkthrough

Step 1: Copy manifests before source

Copy package.json and the lockfile, install dependencies, then copy the rest of the source. The dependency layer is cached and only invalidated when the manifests change, so editing application code does not reinstall packages.

Step 2: Use a cache mount for the package manager

RUN --mount=type=cache,target=/root/.npm npm ci keeps the npm cache outside the layer, so it persists across builds without bloating the image. The cache is for speed; the installed node_modules still lands in the layer as needed.

Step 3: Split build and runtime stages

A multi-stage build compiles in one stage and copies only the build output into a slim runtime stage. Build tools, source maps and dev dependencies never reach the final image, which shrinks it and reduces attack surface.

Worked scenario

The Dockerfile installs with a cache mount and copies source last.

# syntax=docker/dockerfile:1
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]
dockerfile

Walk through the example

The first build downloads packages into the cache mount and produces a dist. On the next build, changing only source files reuses the cached dependency layer and the npm cache, so only the build step reruns. The runtime stage copies just dist, so the final image omits source, dev dependencies and the package cache.

Common mistake

Copying all source before installing dependencies, which invalidates the dependency layer on every change. Another is expecting a cache mount to persist in the pushed image: it lives only in the builder, so anything needed at runtime must be a real layer.

Verify the behavior

Time two consecutive builds and confirm the second is much faster with an unchanged manifest. Change a source file and confirm dependencies are not reinstalled. Inspect the final image size and contents to confirm build tools and the cache are absent.

Interview exercise

Why does COPY . . before RUN npm ci hurt build times?

Answer and reasoning

Docker caches layers by input: any change to the copied context invalidates the COPY layer and every later layer, including the install. Copying the manifests first pins the install to inputs that rarely change, so editing source reuses the cached install. The ordering, not the install command, is what preserves the cache.

Continue learning

Compare layer strategy in Docker layers and cache and multi-stage builds. Read the Docker BuildKit documentation and try the Docker interview questions.

More in Docker

read ✓Docker · mid

Docker Compose Profiles

Start only the services you need with Compose profiles, and keep the default set small for focused local development.

~2 min readread →
read ✓Docker · hard

Docker Distroless and Minimal Images

Ship images without a shell or package manager, copy only the runtime, and accept the debugging trade-off.

~2 min readread →
esc