What's the difference between an image tag and a digest?

6 minintermediateimage-tagsimage-digestversioning

Quick Answer

A tag (e.g., myapp:1.0, myapp:latest) is a mutable, human-friendly pointer that can be reassigned to point at 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 that an image publisher chooses to attach. Nothing stops them from later re-pushing a different image under the exact same tag, and this happens routinely and legitimately. For example, node:20 is regularly updated to include the latest patch releases and security fixes within the Node 20 line, all under that same tag. latest is the most extreme example of this, and also the most commonly misused. It is just a conventional tag name, with no inherent guarantee of being the "most recent" or "most stable" anything. It is simply whatever the publisher most recently tagged as 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 image's content would produce a different hash entirely. Two different tags can point at the same digest (if they happen to reference an identical image), but a single 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 same image is what's running everywhere, every time, with certainty." Examples include a critical production deployment, a security audit that needs to confirm 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. This is because nothing technically prevents 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 simultaneously: a human-readable tag for clarity when a person is looking at the manifest, 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 for this reason. This means a human never has to hand-type a long hash, but the actual deployed reference is still digest-pinned underneath. In particular, latest should never appear in a production deployment manifest at all, since it actively obscures which version is running and offers zero reproducibility guarantee.

Related Resources