Pinning Images by Digest
published v1.0.0stable
A digest is a brick, not a label: it can't be moved, only replaced. Texture: scikit-image sample data, CC0.
- Why image tags drift and digests don't.
- How to pin in Dockerfiles and Kubernetes.
- The multi-arch mistake, and how to keep pins fresh.
A container image tag looks like a version. python:3.12 reads like a promise: this is Python 3.12, and it will stay Python 3.12. It isn't a promise. A tag is a pointer, and whoever controls the registry can move it.
Most of the time that's a feature. Maintainers retag to ship security patches, and a build that pulls python:3.12 next week quietly gets the fix. But it also means two builds of the same commit, a week apart, can produce different images. And if the registry or the tag is ever compromised, the build will pull whatever it is pointed at, without complaint.
Tags move, digests don't
Every image in a registry is described by a manifest, and every manifest has a digest: the SHA-256 hash of its content [1]. Change one byte of the image and the digest changes. That makes the digest an identity, not a label. You can't move it, only replace what you reference.
Pinning in practice
Pinning means referencing images by digest wherever they're consumed: base images in Dockerfiles, images in deployment manifests, images in CI job definitions. You can keep the tag alongside for humans to read. When both are present, the digest is what gets pulled.
# Dockerfile
FROM python:3.12-slim@sha256:<digest>
# find the digest for a tag
crane digest python:3.12-slim
Keep the tag for readability. The digest decides what is pulled.
The same applies in Kubernetes. A pod spec that says image: registry.example.com/app@sha256:… will run that exact image on every node, every restart, every reschedule. A pod spec that says :latest may run something different on each.
The multi-arch catch
Many images are multi-platform. The tag points to an index, a manifest listing one image per architecture. That index has its own digest, and each platform image has another.
Pin the index digest and every platform still resolves correctly: an arm64 node and an amd64 node each get their own image, both fixed. Pin a platform digest by mistake and your arm64 builders will try to run amd64 layers. It's the most common way a well-meant pinning change breaks a pipeline, and it usually happens because someone copied the digest from a local pull rather than from the registry.
Pin the index, not the platform image, unless you mean to.
Keeping pins fresh
The honest objection to pinning is that it freezes you. The patch that the tag would have picked up automatically now needs a change. Left alone, pinned digests rot into a quieter version of the problem they were meant to solve: reproducible builds of vulnerable images.
The fix is to automate the update rather than avoid the pin. Dependency bots handle this well. Renovate, for example, can pin digests [2] and then raise a merge request whenever the upstream tag moves, so every update is a reviewed, visible change instead of a silent one.
{
"extends": ["config:recommended"],
"pinDigests": true
}
A minimal Renovate configuration that pins digests and keeps them current.
Where it fits
Digest pinning is small, cheap, and foundational. It's also the hinge that makes everything else in supply chain security work. Signatures, attestations and SLSA provenance all bind to a digest. If what you deploy is referenced by a tag, there is nothing stable for those guarantees to attach to.
Pin by digest, automate the updates, and verify at deploy. In that order, each step makes the next one meaningful.
- Tags are pointers. Digests are identities.
- Pin the index digest for multi-arch images.
- Automate pin updates so pinning never means stale.
References
- Open Container Initiative, “OCI Image Format Specification.” [Online]. Available: github.com/opencontainers/image-spec
- Renovate, “Docker: Digest Pinning,” Renovate Docs. [Online]. Available: docs.renovatebot.com/docker
Ire o? (Blessings (good fortune to you), Yoruba),
Olarotimi
Revisions
Major versions are rewrites, minor versions add or clarify, patches fix.
- 1.0.0Build step 3c: articles, article layout and the log
- 1.0.0Build step 2: content system (publish by adding a Markdown file)
Every change is recorded on the site log.