Safeguard
Containers

Docker Rebuild Strategies: Cache and Layers Done Right

Docker rebuild speed and security both come down to how you order layers and invalidate cache — get it wrong and you either wait ten minutes per build or ship stale, unpatched images.

Safeguard Team
Product
5 min read

A docker rebuild that takes ten minutes for a one-line code change almost always means the layer cache is being invalidated too early in the Dockerfile — and the same mistake that slows builds down also tends to let stale, unpatched base layers linger far longer than they should. Getting rebuild strategy right is both a velocity problem and a security problem, because a team that dreads rebuilds stops rebuilding often enough to pick up patched base images.

Why does a small code change trigger a full rebuild?

Because Docker's layer cache invalidates every layer after the first one that changes, and most Dockerfiles put COPY . . (which changes on every commit) before the dependency install step, which doesn't need to change nearly as often. Docker builds images layer by layer, caching each layer's result, and reuses cached layers for any build where the preceding instructions are byte-for-byte identical. If your Dockerfile copies the entire source tree before running npm install or pip install, then every single commit invalidates the cache at that copy step — and every layer after it, including the expensive dependency install — even if package.json didn't change.

How should layers actually be ordered?

Order instructions from least-frequently-changed to most-frequently-changed:

FROM node:20-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build

Copying just the manifest files (package.json, package-lock.json) before running the install step means Docker only re-runs npm ci when a dependency actually changes — the far more expensive COPY . . and application build steps happen after, so most commits only rebuild the fast, late layers. This single reordering is usually the biggest rebuild-speed win available in a typical Dockerfile, often cutting rebuild time from minutes to seconds for a routine code change.

When should you deliberately break the cache?

Deliberately busting the cache matters more than most teams assume, specifically for base image freshness. A Dockerfile that never changes will happily reuse a cached FROM node:20-slim layer indefinitely, even though the upstream image has received security patches since your last build. Practical approaches:

  • Rebuild on a schedule (weekly is common) with --no-cache or --pull specifically for the base image layer, independent of code changes, so patched base images actually get picked up.
  • Pin base images by digest and bump the digest deliberately on a cadence, rather than floating on a mutable tag like node:20 that silently changes underneath you.
  • Use CI to alert when a newer, patched version of your pinned base image is available — treat it like a dependency update, because it is one.

What's the actual tradeoff between build speed and image freshness?

Aggressive caching optimizes for speed at the cost of freshness, and aggressive --no-cache rebuilding optimizes for freshness at the cost of speed — the right answer is usually different cadences for different layers, not one setting for the whole build. Dependency and OS-package layers should be rebuilt periodically even without a code change, because their vulnerability exposure changes even when your code doesn't. Application code layers should rebuild on every commit, since that's the layer you control and want fast feedback on. Multi-stage builds help reconcile both: a builder stage can cache aggressively for speed, while the final runtime stage only ships what's needed, keeping the freshness problem scoped to a smaller surface.

How does rebuild strategy connect to vulnerability scanning?

A stale cached image is a scanning blind spot — if you're only scanning newly built images, an image that hasn't been rebuilt in months never gets re-checked against newly disclosed CVEs, even though the same base layer sitting in your registry has accumulated new vulnerabilities since it was last scanned. Scanning needs to run against images already in the registry, not just new builds, and rebuild cadence needs to be frequent enough that a scan finding actually results in a new image, not a stale one nobody re-triggers.

Safeguard's SCA scanning covers container image dependencies alongside application dependencies, so a stale base layer shows up the same way a stale package.json entry does.

FAQ

Does docker build --no-cache rebuild every layer from scratch?

Yes — it ignores the entire layer cache for that build, which guarantees freshness but sacrifices all the speed benefits of caching, so it's usually reserved for scheduled rebuilds rather than every commit.

Do multi-stage builds actually speed up rebuilds?

Indirectly — they don't change caching mechanics, but they let you separate a heavier build/dependency stage (which can cache aggressively) from a lean runtime stage, which keeps the final image small and the security-relevant surface smaller to scan and rebuild.

How often should base images actually be rebuilt?

Weekly is a common baseline for actively maintained services, since upstream OS and language runtime images typically ship security patches at least that often; higher-risk or internet-facing services sometimes rebuild daily.

Does BuildKit change any of this?

BuildKit (Docker's newer build engine) adds more granular caching, parallel stage execution, and cache mount support (for package manager caches specifically), which speeds up rebuilds further but doesn't change the fundamental layer-ordering advice above.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.