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"]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.