Safeguard
Containers

Rebuilding Docker Images: Cache, Patching, and Reproducibility

A plain docker build reuses cached layers and silently skips your security patches. Here is how the cache actually works, how to force a real rebuild, and how to keep rebuilds reproducible.

Safeguard Team
Product
6 min read

To rebuild a Docker image so it actually picks up security patches, run docker build --no-cache --pull: the --no-cache flag forces every layer to re-execute, and --pull re-fetches the base image instead of trusting the stale copy on disk. A plain docker build reuses cached layers, which is exactly what you want for speed during development and exactly what you do not want when the whole point of the rebuild is patching. Most "why is my image still vulnerable" tickets trace back to this one behavior.

Why does a plain docker build skip your patches?

Docker's layer cache is keyed on the instruction text and the parent layer. For a RUN instruction, if the string RUN apt-get update && apt-get upgrade -y is identical to last time and the layers above it have not changed, Docker reuses the cached result. It does not know, and does not check, that the apt repositories now contain newer packages. The instruction looks the same, so the months-old layer gets reused.

COPY and ADD behave differently: they checksum the actual file contents, so changing your application code does bust the cache from that point down. This asymmetry is why app updates flow through fine while OS-level patches silently do not. The base image has the same problem one level up: FROM node:20-bookworm resolves to whatever node:20-bookworm means on your local machine, which may be weeks behind the registry.

How do you force a full Docker image rebuild?

Three levels of force, from mildest to most complete:

  1. docker build --pull -t myapp . re-fetches the base image tag from the registry. Cached layers built on top are invalidated if the base actually changed.
  2. docker build --no-cache --pull -t myapp . re-executes every instruction. This is the correct command when the goal is picking up OS package updates inside RUN steps.
  3. docker builder prune clears the BuildKit cache entirely, useful when you suspect cache corruption or want to reclaim disk.

For Docker Compose the equivalent is docker compose build --no-cache --pull, followed by docker compose up -d to recreate containers from the new image. Note that docker compose restart does not use the new image; containers must be recreated.

Avoid the old trick of injecting a timestamp build argument to bust the cache. It works, but it busts the cache from the argument's position down on every single build, so you lose caching even when you wanted it, and it makes builds non-reproducible for no benefit over the explicit flags.

When should you rebuild a Docker image?

Rebuild on three triggers, not just when application code changes:

  • Base image updates. Official images like debian, alpine, and node are re-released regularly with patched OS packages. If you never rebuild, you never receive those patches, because a container image is a frozen snapshot.
  • New CVEs in your image contents. When a scanner flags a vulnerable OS package or library baked into a layer, the fix is a rebuild against updated sources, not a patch inside running containers.
  • A schedule. Weekly scheduled rebuilds of production images are cheap insurance. They cap the maximum staleness of every package in the image even when nobody is shipping features.

Teams that treat images as immutable artifacts sometimes conclude they should never rebuild. The correct reading is the opposite: because you cannot patch a running image in place, rebuild-and-redeploy is the only patching mechanism you have.

How do you keep rebuilds reproducible?

A rebuild that pulls "whatever is newest" trades reproducibility for freshness. You can have a controlled amount of both:

  • Pin base images by digest (FROM node:20-bookworm@sha256:...) in the Dockerfile, and update the digest deliberately, ideally via an automated PR. You get byte-identical bases between builds, and an audit trail when the base changes.
  • Use lockfiles for application dependencies (package-lock.json, poetry.lock, go.sum) so a cache-busted rebuild does not silently jump library versions along with OS packages.
  • Separate the two update streams. OS patches arrive via the digest bump; app dependency updates arrive via lockfile changes. When a rebuild changes behavior, you can tell which stream caused it.
  • Use multi-stage builds so build tooling never lands in the runtime image. Smaller final images mean fewer packages to patch and faster rebuilds.

Where does rebuild cadence meet vulnerability scanning?

Scanning and rebuilding are two halves of one loop. A scanner tells you which layers contain vulnerable packages; the rebuild is the remediation. If scan results do not feed a rebuild trigger, you accumulate known-vulnerable images that everyone has agreed to fix "later". Wiring SCA and container scanning into CI closes the loop: the pipeline flags the vulnerable base or dependency, the digest or lockfile gets bumped, and the rebuild ships the fix. Safeguard's scanning takes this approach, tracking whether a flagged package is actually fixable by a newer base image so rebuild work is spent where it pays off.

For a deeper look at what image scanners catch and miss, see the rest of our container security posts.

FAQ

Does docker build --no-cache also pull a fresh base image?

No. --no-cache only forces instructions to re-execute against the base image you already have locally. Add --pull to re-resolve the base tag from the registry. For patching purposes you almost always want both flags together.

How do I rebuild a single service in Docker Compose?

Run docker compose build --no-cache --pull servicename, then docker compose up -d servicename. The up step is required because restarting a container does not switch it to the newly built image.

Why does my rebuilt image still show old package versions?

Usually one of three causes: the base image is pinned to an old digest, a RUN apt-get upgrade layer was served from cache because you omitted --no-cache, or your registry mirror is serving a stale base tag. Check docker history on the image to see which layers were rebuilt.

How often should production images be rebuilt?

Weekly is a sensible default, plus immediate rebuilds when a scanner flags a critical CVE with an available fix. The cadence caps how stale any package in the image can get, independent of your feature release schedule.

Never miss an update

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