skip to content

A team deploys containers using the image tag `:latest`. What problems does that cause, and what would you use instead?

level: juniorimportance: must knowfreq 65%

answer

  1. latest = default tag, not newest push
  2. no rollback target, no identity
  3. pull policy makes redeploy a no-op
  4. FROM x:latest = unreviewed build inputs
  5. exact tags immutable; rolling tags may move

basics

~20 s

:latest is an ordinary mutable tag with no special meaning — it is not "the newest image". Different hosts can run different content under the same name, rollback has nothing to roll back to, and caching makes it unpredictable. Deploy explicit version tags resolved to digests.

solid answer

~60 s

`latest` is not a feature; it is just the tag Docker assumes when you omit one. It is not automatically the newest push, it is only whatever was last pushed *with that tag*, and it can be repointed at anything. The damage in production: - **No identity.** Two nodes pulling `:latest` an hour apart can run different builds while reporting the same image name. Incidents become unreproducible. - **No rollback.** "Go back to the previous image" has no name to go back to. - **Cache ambiguity.** A host that already has a `latest` layer set may or may not re-pull, depending on pull policy, so a "redeploy" may change nothing — or change everything. - **Unreviewable base images.** `FROM ubuntu:latest` means your build inputs change without a commit. Instead: tag every build with something immutable and traceable — a semver like `1.4.2` and/or the git commit SHA — let CI resolve that tag to a digest, and deploy the digest. Keep `latest` if you like, but only as a convenience pointer for humans, never as a deploy target.

code

bash · 9 lines
bash
GIT_SHA=$(git rev-parse --short HEAD)
docker build -t registry.example.com/myapp:1.4.2 \
             -t registry.example.com/myapp:git-$GIT_SHA .
docker push registry.example.com/myapp:1.4.2
docker push registry.example.com/myapp:git-$GIT_SHA

DIGEST=$(docker inspect --format '{{index .RepoDigests 0}}' \
         registry.example.com/myapp:1.4.2)
echo "deploying $DIGEST"   # registry.example.com/myapp@sha256:...

go deeper

for a junior

Know that latest is just the default tag name and that you should deploy explicit version tags.

for a middle

Explain pull-policy interaction and cache behaviour, and why unpinned FROM lines make builds non-reproducible.

for a senior

Talk about rollback targets, incident forensics, tag immutability enforcement in the registry, and CI resolving tags to digests.

for a principal

Position it as artifact-identity policy across the org: immutable release tags, digest-based admission control, and dependency bots turning base-image drift into reviewed commits.

## What `latest` actually is There is a persistent myth that `latest` is a registry-computed alias for the most recently pushed image. It is not. Docker's reference parser simply defaults the tag to `latest` when you type `docker pull nginx`. The registry treats `latest` exactly like `v3`, `stable`, or `banana`: a string key in the repository's tag table. If you push a two-year-old image with `-t myapp:latest`, then `myapp:latest` *is* that two-year-old image. Many well-run repositories deliberately do not publish a `latest` tag at all. ## The failure modes **Loss of identity.** Deployment systems record what they deployed. If the recorded artifact is `myapp:latest`, the record carries zero information — it does not distinguish the build that worked at 09:00 from the build that broke at 14:00. When you page someone at 03:00 and ask "what's running?", the honest answer is "something". Nodes that started at different times can legitimately be running different code under one name, which produces the worst class of bug: behaviour that varies by instance with no visible cause. **No rollback target.** Rollback is "deploy the previous artifact". A mutable tag has no previous. You can sometimes recover the prior digest from registry history or from a node that still has the old layers, but you are now doing forensics during an outage instead of running one command. **Pull-policy ambiguity.** Whether a host re-pulls depends on policy, not on the tag's content. In plain Docker, `docker run myapp:latest` uses a locally present image without checking the registry unless you `docker pull` first or pass `--pull always`. Orchestrators have their own default-by-tag rules. The result is that "restart the container to pick up the new build" works on a fresh node and silently does nothing on a warm one — a classic "it deployed but nothing changed" ticket. **Uncontrolled build inputs.** `FROM python:latest` means the compiler, libc, and default packages under your application can change between two builds of the same commit. When a build starts failing with no code change, an unpinned base tag is the first suspect. Worse is the silent case: the build succeeds and the runtime behaviour changes. **Concurrency races.** With several pipelines pushing `latest`, the last writer wins, and a deploy that starts between two pushes can pull a version nobody intended to ship. ## What to do instead **Give every build an immutable name.** Two schemes are commonly combined: - *Traceability tag*: the git commit SHA, e.g. `myapp:git-9f3c1ab`, optionally with a build number. This is unique per build and maps straight back to source. - *Human/version tag*: semantic version, e.g. `myapp:1.4.2`, plus rolling convenience pointers `1.4` and `1` that intentionally move. The rule that keeps this sane: **exact tags never move; rolling tags may move; only exact tags are deploy targets.** **Resolve to a digest at deploy time.** CI pushes `myapp:1.4.2`, immediately reads back the digest, and hands the digest to the deployment. The tag chose the artifact; the digest *is* the artifact. Store the digest in the deployment record, the release notes, and the change ticket. **Pin base images.** Write `FROM python:3.12.4-slim@sha256:…`. The tag stays for readability, the digest is authoritative, and a dependency bot proposes the bump as a reviewable commit. **Enforce it if you can.** Many registries support *tag immutability* (a push to an existing tag is rejected). Turn it on for release repositories. Admission policies in orchestrators can reject any workload whose image reference has no digest or uses `latest`. **Keep `latest` only as a courtesy.** Publishing `latest` for humans running a quick `docker run` is fine and expected for public images. Just never let a pipeline, a manifest, or a `FROM` line depend on it. ## The one-line summary for an interview `latest` is a mutable pointer with a misleading name; production needs an immutable, traceable reference, and the cheapest way to get one is an exact version tag plus the digest CI resolves it to.

  • Is it ever acceptable to publish a `latest` tag?
    Yes — for public or developer-facing images it is a useful convenience so people can run the thing without looking up a version. The rule is about consumption, not publication: nothing automated should depend on it. Publish `latest` alongside exact version tags, and keep deployments and `FROM` lines on the exact tags or digests.
  • A team switched from `:latest` to `:1.4.2` but still sees drift between hosts. What else could be wrong?
    Either the version tag is being repushed (someone rebuilt and re-tagged `1.4.2`, which a registry will happily accept unless tag immutability is enabled), or hosts are running cached images and the pull policy never re-checks. Turn on tag immutability so an overwrite is rejected, and deploy by digest so caching becomes correct rather than ambiguous.

Deploying :latest is like shipping from a branch called main with no commit SHA recorded — you know roughly the intent, but not what actually went out.

saying these in an interview costs you the question

  • Saying the registry automatically points `latest` at the most recent push
  • Claiming `:latest` always triggers a fresh pull
  • Treating a semver tag as immutable by nature when nothing stops it being repushed
  • Suggesting the fix is to always `docker pull` before running, rather than to use an immutable reference
  • Believing `latest` is required and every repository must publish it

context