What's the difference between using a tag and using a digest for pinning?

6 minintermediateimage-pinningdigestsreproducibility

Quick Answer

This mirrors the earlier tags-vs-digests question in the Images/Builds topic, applied specifically to how you reference images from other systems (deployment manifests, CI pipelines, base images). 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. Pinning to a digest (rather than just a version-looking tag like 1.0.3) is the only way to guarantee true reproducibility — nothing technically prevents a tag from being silently repointed 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 in the registry's technical model prevents the maintainers of the node image from re-pushing different content under that exact same tag later — for a critical security patch to an already-released tag, for instance, which does legitimately happen. A tag, no matter how specific it looks, is still fundamentally a mutable pointer, not a permanent 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 exact reference either resolves to the exact same bytes every time, or fails outright if that specific content is no longer available at all, rather than silently substituting 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 provides a real guarantee, for anything where you need absolute certainty about exactly what's being built or deployed. This includes a security audit needing to confirm precisely what code is running, a compliance requirement for reproducible builds, or simply wanting to eliminate an entire class of "it worked yesterday, broke today with no code changes on our end" 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}}'

Many teams don't hand-write digest references directly in source-controlled Dockerfiles, since that would be unreadable and hard to update deliberately. Instead, CI/CD automation resolves and locks in the actual digest at build time, often recording it in a lockfile-like artifact. This gives both human readability during everyday development and true reproducibility for what's actually built and shipped.

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 involves a real tradeoff: you gain perfect reproducibility, but you also lose the often-desirable behavior of automatically picking up upstream security patches published under a broader, still-actively-maintained tag. This is why many teams pin digests specifically for final, deployed production artifacts, while still tracking broader version tags for base images used during ongoing development — with active dependency-update tooling, like Dependabot or Renovate, opening pull requests when new patch versions or digests are available.

Related Resources