Enable BuildKit by setting DOCKER_BUILDKIT=1 or using docker buildx build (BuildKit is the default backend in recent Docker versions)
Structure the Dockerfile with a named build stage for dependency installation: FROM node:20-alpine AS deps
In the deps stage, use a cache mount for the package manager cache directory: RUN --mount=type=cache,target=/root/.npm npm ci — the cache persists across builds on the same builder instance without being written into the image layer
Copy only the built artifacts into a lean final stage: FROM node:20-alpine AS runner followed by COPY --from=deps /app/node_modules ./node_modules and COPY --from=deps /app/dist ./dist
For CI, export the cache to a registry backend: docker buildx build --cache-to type=registry,ref=ghcr.io/<org>/cache:myapp --cache-from type=registry,ref=ghcr.io/<org>/cache:myapp -t ghcr.io/<org>/myapp:latest --push .
Verify layer cache hits with docker buildx build --progress=plain and look for CACHED in the step output
Known gotchas
--mount=type=cache mounts are builder-instance-local by default; in ephemeral CI runners where a new builder is provisioned per job, the cache is empty unless a registry-backed cache-from is explicitly configured
Cache mounts do not invalidate when the Dockerfile or source files change — they are keyed by the mount target path. If a dependency installation writes files that affect the build result outside the mount path, you may get stale artifacts
COPY --from=<stage> copies the filesystem state of the named stage at the point instructions completed; if an intermediate stage uses a secret mount (--mount=type=secret), that data does not persist into the copy and must not be included in any COPY
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp