What's the difference between an image tag and a digest?
Quick Answer
A tag (e.g., myapp:1.0, myapp:latest) is a mutable, human-friendly pointer that can be reassigned to a different underlying image at any time — pulling myapp:latest today and tomorrow could give you two genuinely different images. A digest (myapp@sha256:abc123...) is an immutable, content-addressed identifier computed from the image's actual contents. It always refers to exactly one specific image, forever, and is what you should reference when reproducibility genuinely matters — production deployments, security-sensitive pinning.
Detailed Answer
Tags are mutable pointers, not permanent identifiers
docker pull node:20
# some time later, after the maintainers push a new patch-level build of node:20...
docker pull node:20
# this can silently give you a DIFFERENT image than before, with the same tag
A tag is just a label an image publisher chooses to attach. Nothing stops them from later re-pushing a different image under the exact same tag — this happens routinely and legitimately. For example, node:20 gets regularly updated with the latest patch releases and security fixes within the Node 20 line, under that same tag. latest is the most extreme example, and the most commonly misused. It's just a conventional tag name, with no guarantee of being the "most recent" or "most stable" anything — just whatever the publisher most recently tagged latest.
Digests are immutable, content-addressed identifiers
docker pull node:20
docker inspect node:20 --format='{{index .RepoDigests 0}}'
# node@sha256:a1b2c3d4e5f6...
docker pull node@sha256:a1b2c3d4e5f6...
A digest is a cryptographic hash computed from the image's actual manifest and content. Pulling by digest always gives you the exact same bytes, forever, since any change to the content produces a different hash entirely. Two different tags can point at the same digest if they reference an identical image, but one digest can never refer to two different images.
Why this distinction matters for reproducibility
# Fragile: this could resolve to a DIFFERENT image tomorrow than it did today
image: myapp:1.0
# Fully reproducible: this ALWAYS refers to the exact same image, forever
image: myapp@sha256:a1b2c3d4e5f6...
Sometimes you need a guarantee that "this exact image is running everywhere, every time, with certainty" — a critical production deployment, a security audit confirming exactly what code is running, or a supply-chain-security pinning requirement. In these cases, a mutable tag alone doesn't provide that guarantee, even a specific version-looking tag like 1.0.3, because nothing stops someone from re-pushing different content under that same tag later.
The practical middle ground most teams use
image: myapp:1.0.3@sha256:a1b2c3d4e5f6...
Combining both gives you readability and reproducibility at once: a human-readable tag for clarity, plus the digest for the actual immutable guarantee the deployment relies on. Many CI/CD pipelines automatically resolve and pin the digest at build or deploy time, so a human never has to hand-type a long hash while the actual deployed reference stays digest-pinned underneath. latest in particular should never appear in a production deployment manifest — it actively obscures which version is running and offers zero reproducibility guarantee.