What's the difference between using a tag and using a digest for pinning?
Quick Answer
A tag is a mutable, human-readable pointer that can be reassigned to different content over time. A digest is an immutable, content-addressed hash that always refers to exactly the same image, forever. This matters most when you reference images from other systems — deployment manifests, CI pipelines, base images. Pinning to a digest, rather than a version-looking tag like 1.0.3, is the only way to guarantee true reproducibility, since nothing stops a tag from silently pointing at different content later.
Detailed Answer
Why even a specific-looking version tag isn't a true guarantee
FROM node:20.11.0-slim
This looks precisely pinned — a specific patch version, not a broad 20 or latest. But it's still just a tag. Nothing stops the maintainers of the node image from re-pushing different content under that exact tag later — for example, a critical security patch to an already-released tag. This does happen. A tag, no matter how specific it looks, is a mutable pointer, not a guarantee of identical content.
Digest pinning — the actual guarantee
FROM node@sha256:a1b2c3d4e5f6789...
This reference can never silently change. The digest is a cryptographic hash of the image's actual content, so any change to that content produces a completely different hash. This reference either resolves to the exact same bytes every time, or fails outright if that content is no longer available — it never silently substitutes something different.
Where this matters most: supply-chain security and reproducible builds
# CI/CD pipeline, or a Kubernetes manifest, deploying a base or dependency image
image: node@sha256:a1b2c3d4e5f6789...
Digest pinning is the only mechanism that guarantees exactly what's being built or deployed. This matters for a security audit confirming precisely what code is running, a compliance requirement for reproducible builds, or just eliminating "it worked yesterday, broke today with no code changes" incidents caused by an upstream base image silently changing under an unchanged tag.
The common middle-ground practice: automated digest resolution
# A CI pipeline step that resolves a tag to its current digest at build time,
# then uses that resolved digest for the actual deployment reference --
# giving humans the readability of a tag during development, while the
# ACTUAL deployed/built reference is digest-pinned underneath
docker pull node:20.11.0-slim
docker inspect node:20.11.0-slim --format='{{index .RepoDigests 0}}'
Most teams don't hand-write digest references directly in Dockerfiles — that would be unreadable and hard to update. Instead, CI/CD automation resolves and locks in the actual digest at build time, often recording it in a lockfile-like artifact. This gives you both readable tags during development and true reproducibility for what actually ships.
The tradeoff: you lose automatic security patches
FROM node:20-slim # gets security patches automatically on rebuild, but is a moving target
FROM node@sha256:abc123... # never changes, but you must manually update this reference
# to actually receive newer base-image security patches
Digest pinning is a real tradeoff: you gain perfect reproducibility, but lose the automatic security patches you'd get from a broader, actively-maintained tag. Many teams pin digests only for final, deployed production artifacts, while still tracking broader version tags for base images used during development — with dependency-update tooling like Dependabot or Renovate opening pull requests as new patch versions become available.