Fix docker build re-running npm install on every code change (layer cache)
The problem
Every one-line source edit produced a 4-minute build instead of a 10-second one. The image had COPY . . sitting ABOVE RUN npm install, so any source change invalidated that layer and the full dependency install re-ran from scratch on every build.
What didn't work
docker build --cache-from <old-image>— can't help when the layer's actual inputs changed; the cache key is content-based.- Adding a
.dockerignoreafter the fact — shrinks the context, doesn't fix the ordering, so edits still bust the install layer. - BuildKit cache mounts on the install alone —
--mount=type=cache,target=/root/.npmhelps the download step, but npm still re-resolves the whole lockfile on every build.
The fix
FROM node:22-alpine
WORKDIR /app
# 1. copy ONLY the manifests -> this layer survives source edits
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
# 2. copy source and build -> only these layers re-run on a code change
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"]
# .dockerignore — without it, .git churn and the host's node_modules bust every layer
.git
node_modules
dist
.env
Why it works
Docker invalidates a layer only when its inputs change; copying the manifests and lockfile first isolates the dependency install from your source tree, so npm ci reuses the warm layer (and the npm download cache) on every code-only change.