Independent stages run at the same time: two stages that each print the time and sleep five seconds logged right starts 10:36:34 and left starts 10:36:34 on this host. docker buildx bake (buildx bake) extends this to several targets at once.
A cache mount, RUN --mount=type=cache,target=/root/.npm, gives a step a directory that persists between builds but never becomes part of a layer, so npm 2,036 keeps its downloads even when a changed lockfile reruns npm ci. Prove it by reinstalling BookNest's dependencies with the network switched off:
# syntax=docker/dockerfile:1
FROM node:24-slim
WORKDIR /app
COPY package.json package-lock.json ./
# A new RUN_ID forces this step to run: --no-cache would also empty the cache mount.
ARG RUN_ID
RUN --network=none --mount=type=cache,target=/root/.npm \
du -sh /root/.npm/_cacache && npm ci --omit=dev --offline --no-audit --no-funddocker build --progress=plain --build-arg RUN_ID=$RANDOM -f ../offline.Dockerfile \
-t l3-offline . 2>&1 | grep -E '^#[0-9]+ [0-9.]+ .*[a-z]'#10 0.118 2.5M /root/.npm/_cacache #10 1.165 added 82 packages in 939ms
Cache mounts are keyed by id, which defaults to the target path, so Dockerfiles mounting /root/.npm share one cache (sharing=locked serializes writers). Measured here: --no-cache and --no-cache-filter start cache mounts empty too, and BookNest's 82 small packages installed in about 2 seconds with or without the cache; large trees and slow links gain far more.