A Docker host keeps running the old image after you pushed a patched rebuild to the same tag — why?
answer
- A tag is a name, not the bytes
- The host already has something for it
- A container is bound to an image ID
- Restart is not the same as recreate
- Deploy an immutable reference instead
basics
~20 sA tag is a mutable pointer, and the host already holds an image under that name, so docker run, docker compose up and docker restart never ask the registry. Only a pull plus a container recreate lands the rebuild.
solid answer
~40 sPushing new bytes under an existing tag changes nothing on machines that already resolved it. `docker run` defaults to `--pull missing`, so it uses the local image; `docker restart` and a `--restart` policy restart the same container with the same image ID; `docker compose up` reuses the local image unless told otherwise. The fix is explicit: `docker pull` the tag (or `docker run --pull always`, or `docker compose pull` then `up -d`) and **recreate** the container — restarting is not enough, because a container is bound to the image ID it was created from. Verify rather than assume: compare `docker image inspect --format '{{index .RepoDigests 0}}'` on the host against the digest the build published. The structural fix is to stop deploying mutable tags and deploy a unique tag or a digest per build.
code
bash · 5 lines# What digest did this host actually pull for that tag?
docker image inspect --format '{{index .RepoDigests 0}}' recon-worker:4.7.3
# What image ID is the RUNNING container on?
docker inspect --format '{{.Image}}' recon-workergo deeper
Recall that pushing a new image under an existing tag does not change what is already running, and that you have to pull the image and create the container again. Restarting a container reuses the image it already has.
Explain the mechanism: the default missing pull policy, a container bound to an image ID at creation, and Compose recreating only on configuration change. Know the pull-then-recreate commands and how they differ from restart.
Demonstrate verification across a fleet: compare repo digests on hosts against the published digest, check the image ID a running container is on, and treat a base-only rebuild as a real deployment with tests and a staged rollout.
Own the addressing standard. Decide that production is deployed by digest or by unique per-build tag, so a rollout is a reviewable change rather than a push to a moving name, and make sure disk reclamation on hosts is part of that policy.
## Two separate things called "the image" A registry reference like `recon-worker:4.7.3` is a name. What actually runs is a specific image, identified by a content digest, and a container is bound to the image **ID** it was created from — not to the name you typed. Rebuilding and pushing under the same tag changes what the name means in the registry and changes nothing about the images already on your hosts or the containers already created from them. ## Why each command leaves you on the old bytes - `docker run recon-worker:4.7.3` uses the default pull policy `missing`: it consults the registry only when no local image matches the reference. The host pulled 4.7.3 eleven days ago, so it runs those bytes. - `docker restart <container>` stops and starts the **same container object**, whose root filesystem and image ID were fixed at creation. It cannot pick up a new image; neither can a `--restart` policy firing after a crash or a host reboot. - `docker compose up -d` recreates a service only when its configuration has changed. If the compose file still says the same image reference and Compose sees a local image for it, it can legitimately leave the running container alone. The result is a fleet that quietly splits: hosts provisioned after the push run the patched image, hosts provisioned before it run the vulnerable one, and both report the same tag in `docker ps`. On a payments reconciliation batch this is the worst kind of drift, because the two populations differ only in the base layers and behave identically until something audits them. ## Making the rebuild land Pull, then recreate: ```bash docker pull recon-worker:4.7.3 docker rm -f recon-worker && docker run -d --name recon-worker recon-worker:4.7.3 ``` Or force the policy at run time with `docker run --pull always`. With Compose, `docker compose pull && docker compose up -d` fetches the new bytes and recreates the services whose image ID changed; add `--force-recreate` when you want the recreation to happen regardless. The rule to internalise: **pull refreshes the image, recreate refreshes the container, restart refreshes neither.** ## Verify, do not assume On a host, `docker image inspect --format '{{index .RepoDigests 0}}' recon-worker:4.7.3` shows the registry digest those local bytes came from; compare it to what the build published. To check what a *running* container is actually on, `docker inspect --format '{{.Image}}' recon-worker` gives the image ID it was created from — if that ID is not the ID of the freshly pulled image, the container is old no matter what its tag says. This is the check that turns "we rolled out the patch" into evidence. After a same-tag replacement the previous image loses its name and shows up as `<none>` in `docker image ls`. It is still on disk, still referenced by any container created from it, and worth reclaiming with `docker image prune` once nothing runs on it — a fleet that pulls a roughly 2.3 GB image weekly and never prunes fills its disks on schedule. ## The structural fix Every step above is a workaround for having deployed a mutable name. If each build publishes a unique, immutable reference — `recon-worker:4.7.3-b2183`, or better the digest `recon-worker@sha256:...` — then "same tag, different bytes" cannot happen: changing what runs is a change to the reference, which is visible in a diff, reviewable, and trivially auditable across a fleet. Deployment systems that reconcile a declared reference against what is running make the pull-and-recreate cycle automatic, but they inherit the same rule: an immutable reference is what makes the intent unambiguous, and a mutable tag is what makes a rollout unverifiable. One more habit is worth naming: a patched rebuild is a deployment, with the same testing and staged rollout as any other. The base changed underneath a C++ daemon that links against the runtime shared libraries in it — a refreshed base is not a no-op just because your source did not change.
- Why does `docker restart` never pick up a new image, even after a successful `docker pull`?Because `docker restart` acts on an existing container object, whose image ID and root filesystem were fixed when it was created. Pulling adds a new image to the host's store but does not re-create anything. The container has to be removed and created again — or recreated by whatever manages it — before it runs the new bytes.
- What is left behind on the host after a same-tag replacement, and does it matter?The previous image loses its tag and appears as `<none>` in `docker image ls`. It stays on disk and is still referenced by any container created from it, so it cannot be removed while those run. It matters for disk: a fleet pulling a multi-gigabyte image on a schedule needs `docker image prune` (or a retention policy) or it will fill its filesystems.
- Why is deploying `myapp@sha256:...` easier to audit than deploying `myapp:1.4`?A digest is content-addressed: one reference can only ever mean one set of bytes, so what is deployed is visible in the change itself and identical on every host. With a mutable tag, two hosts can run different images while reporting the same name, and proving what is actually running requires inspecting each host rather than reading the deployment.
- Should a rebuild that only refreshes the base skip the normal release process?No. The base changed underneath your binaries — shared libraries, a libc, a TLS stack, sometimes a default configuration. For a natively compiled service that is a real change in behaviour risk even though your source is identical. Run the same tests and the same staged rollout you would for a code change; the small size of the diff in your repository is not a measure of the risk.
saying these in an interview costs you the question
- Thinks docker restart pulls the newer image
- Assumes docker run always checks the registry
- Believes pushing a tag updates running hosts
- Confuses the image tag with the image ID
- Calls a base-only rebuild a zero-risk deploy
- Never prunes the untagged images left behind