skip to content

What does `docker build --pull` do, and why can a nightly rebuild still ship an outdated base image?

level: juniorimportance: should knowfreq 42%

answer

  1. The base is looked up locally first
  2. A tag is a moving pointer
  3. One flag re-checks the registry
  4. --no-cache is not --pull
  5. A digest pin makes --pull inert

basics

~20 s

docker build --pull makes the builder re-resolve every FROM reference against the registry instead of reusing the base image already in the host's local image store. Without it a rebuild can inherit a months-old base, and its CVEs, forever.

solid answer

~50 s

A tag like `debian:12-slim` is a mutable pointer: the maintainer rebuilds it with patched packages and moves the tag. Your builder, though, looks in the daemon's local image store first, so on a long-lived build host the `FROM` line can keep resolving to the copy pulled months ago. `docker build --pull` tells the builder to attempt to pull a newer version of every image a `FROM` (or `COPY --from=<image>`) names, replacing the local copy when the tag now points at a different digest. `--no-cache` is not a substitute: it stops the reuse of previously built instruction layers, but it does not re-resolve the base, so you re-run every `RUN` on top of the same stale bytes. If the `FROM` is pinned to a digest, `--pull` cannot refresh anything — a digest names exact content, and only an edit to the Dockerfile moves it.

code

bash · 8 lines
bash
# Refresh every FROM reference from the registry, then build
docker build --pull -t recon-worker:4.7.3 .

# --no-cache alone re-runs the instructions on top of the SAME cached base
docker build --no-cache -t recon-worker:4.7.3 .

# Scheduled rebuild: fresh base AND no reused instruction layers
docker build --pull --no-cache -t recon-worker:4.7.3 .

go deeper

for a junior

Recall that the base image can come from the machine's local copy, and that docker build --pull is what asks the registry for a newer one. Know that --no-cache is a different flag solving a different problem.

for a middle

Explain the mechanics: a tag is a mutable pointer to a manifest digest, the builder resolves it locally first, and --pull forces re-resolution. Be able to say precisely what --no-cache invalidates and what it leaves alone.

for a senior

Show that you verify rather than assume: capture the resolved base digest per build, know that persistent runners and shared builders are where staleness hides, and make --pull an explicit part of any scheduled rebuild rather than trusting a default.

for a principal

Own the standard: decide whether the estate builds from mutable tags refreshed with --pull or from digests bumped by automation, and make sure the choice is enforced in shared pipeline templates rather than rediscovered by each team after a bad scan report.

## The default that surprises people When the builder reads `FROM debian:12-slim`, it needs bytes for that reference. It looks in the Docker daemon's local image store first. If an image tagged `debian:12-slim` is already there — pulled once, weeks or months ago, on a self-hosted CI runner or a developer laptop — those are the bytes the build uses, and the registry is never consulted. That single fact explains most reports of the shape "we rebuild every night and the image scanner still reports the same findings". ## What a tag actually is A tag is a **mutable pointer** inside a repository: a human-readable name that currently resolves to one manifest digest. Base-image maintainers rebuild their images regularly with patched OS packages and move the tag to the new manifest. The bytes behind `debian:12-slim` in March are not the bytes behind it in June, even though the string never changed. Nothing pushes that change to you; a client only learns the pointer moved by asking the registry. ## What `--pull` does `docker build --pull` instructs the builder to always attempt to pull a newer version of every image referenced by the build — the `FROM` of each stage, and any `COPY --from=<image>` that names an external image. If the tag now resolves to a different digest, the new manifest and its layers are fetched and become what the build uses. Nothing else changes: the per-instruction build cache still applies, so a build where the base has not moved is a normal fast build. It is a freshness switch, not a "rebuild everything" switch. ## What `--no-cache` does, and does not do `--no-cache` disables reuse of previously built layers for the instructions in your Dockerfile: every `RUN`, `COPY` and `ADD` executes again. It does **not** re-resolve the base image. A build with `--no-cache` and no `--pull` faithfully re-runs your whole Dockerfile on top of the same outdated base — the most expensive way to produce the same vulnerable image. The two flags are orthogonal and are often used together in a scheduled rebuild. ## When the FROM is pinned to a digest If the Dockerfile says `FROM debian:12-slim@sha256:...`, the reference is content-addressed: that digest can only ever mean those exact bytes. `--pull` then degenerates into a fetch-if-missing plus an integrity check — it cannot make the base newer. Freshness for a digest-pinned base comes from something editing the Dockerfile to a newer digest, typically an automated update pull request. Pinning buys you reproducibility and pays for it with a refresh path you now have to own. ## Where this bites in practice A payments reconciliation batch ships as a C++ daemon with a fat runtime-shared-library layer, roughly a 2.3 GB image, rebuilt nightly on a self-hosted runner with a persistent Docker daemon. The runner pulled `debian:12-slim` 63 days ago. Every nightly build reuses it, so an image scanner keeps reporting the same 41 critical findings and the team concludes the scanner is broken. Adding `--pull` to the build resolves the tag to the current, rebuilt base, and the same scan drops to 6 findings — none of the Dockerfile changed. The classic builder and BuildKit differ in the details of when they re-check a tag, which is exactly why CI pipelines pass `--pull` explicitly rather than relying on a default they cannot see. Ephemeral runners that start with an empty image store hide the problem, because there is nothing local to reuse; the moment you add a cache or a persistent host to speed builds up, the problem appears. ## The habit to build A scheduled rebuild that exists to pick up base-image fixes should say so in its command line: pull the base, and verify afterwards which base you actually got. `docker image inspect --format '{{index .RepoDigests 0}}' debian:12-slim` on the build host tells you the digest that was used, and comparing it week to week is a cheap check that the refresh is real rather than assumed.

  • Why do teams often see this only after they add a self-hosted or caching build runner?
    A fresh, ephemeral runner starts with an empty image store, so every build has to pull the base anyway and stays accidentally current. Adding a persistent host, a warm daemon or a shared builder to speed builds up introduces a long-lived local copy of the base, and from then on the `FROM` tag resolves locally until something forces a re-check.
  • Does `docker build --pull` guarantee the newest base image in every case?
    No. It guarantees the tag is re-resolved against the registry, which is only as fresh as the tag itself. If the `FROM` is digest-pinned, `--pull` cannot move it. If you build through a pull-through mirror, you get what the mirror has cached. And if the upstream maintainer has not rebuilt the tag, re-resolving it correctly returns the same digest you already had.
  • How would you prove to a sceptical reviewer that last night's build really used a new base?
    Compare digests, not dates. Record the base digest each build resolved — from `docker image inspect` on the builder, or from the build log — and diff it against the previous run. A changed base digest with an unchanged Dockerfile is exactly the evidence that the refresh worked; an unchanged digest means either the upstream tag has not moved or the pull never happened.

A tag is like a bookmark to "today's newspaper". Rebuilding without --pull is re-reading the copy already on your desk; the bookmark points at a newer edition, but nobody walked to the shop.

saying these in an interview costs you the question

  • Thinks --no-cache also re-pulls the base image
  • Assumes a rebuild always fetches the latest base
  • Believes a tag always points at the same bytes
  • Says --pull re-runs every Dockerfile instruction
  • Expects --pull to refresh a digest-pinned FROM
  • Blames the image scanner instead of checking the base digest

context