Base Image Pinning by Digest
Summary
- Pinning by digest means referencing a base image by its immutable
sha256content hash instead of a mutable tag likelatestor1.22. - Insight: A tag is a pointer that can move under you. A digest is the bytes themselves, so the build you run today is the build you ran last month.
- Key Concepts: digest (
@sha256:...), tag mutability, rebuild cadence, reproducibility, and automated bump PRs. - When to Use: Pin every production base image by digest, and pair the pin with a scheduled rebuild so security patches still flow in.
- Limitations: A pinned digest freezes vulnerabilities as well as behavior. Without a rebuild cadence you ship stale, unpatched bases.
Recipe
Resolve the tag you use to its current digest.
docker pull python:3.13-slim
docker inspect --format='{{index .RepoDigests 0}}' python:3.13-slimCopy the returned digest into your Dockerfile FROM line.
FROM python:3.13-slim@sha256:2f1e...c9abCommit that Dockerfile. Every build from now on uses exactly those bytes until you change the digest on purpose.
Working Example
Keep the human-readable tag as a comment so reviewers know what the digest represents.
# python:3.13-slim as of 2026-07-15
FROM python:3.13-slim@sha256:2f1e0b3a5c7d9e11223344556677889900aabbccddeeff00112233445566c9ab
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER 65532:65532
ENTRYPOINT ["python", "-m", "app"]Enforce the pin in CI so no one sneaks in a floating tag.
# Fail the build if any FROM uses a tag without a digest
grep -E '^FROM ' Dockerfile | grep -qv '@sha256:' && {
echo "Base image not pinned by digest"; exit 1; }Automate the bump so pinning does not mean staleness. Renovate or Dependabot opens a PR when a new digest is published.
{
"extends": ["config:recommended"],
"docker": { "pinDigests": true }
}Renovate rewrites the @sha256: value and updates the comment, and your CI scan runs against the new digest before merge.
Deep Dive
Why tags are unsafe as a sole reference
A tag is a mutable label in the registry. The maintainer can push new bytes to python:3.13-slim at any time, so two builds a week apart can differ silently.
That is convenient for getting patches but terrible for reproducibility and incident forensics. You cannot answer "what exactly was running" from a tag alone.
What a digest actually is
The digest is a sha256 hash of the image manifest, which in turn references the config and layer digests. Change one byte in any layer and the manifest digest changes.
Because the registry stores content addressed by that hash, @sha256:... can never point at different bytes. It is immutable by construction.
The pinning and rebuild pairing
Pinning solves reproducibility. It does nothing for security on its own.
The base maintainer patches a CVE by publishing new bytes under the same tag with a new digest. If your Dockerfile still names the old digest, you never receive the fix.
So the pattern is two-part: pin by digest for reproducibility, and run automated digest bumps plus scheduled rebuilds for security. One without the other is a half measure.
Multi-arch and digests
A tag on a multi-arch base points at an image index whose digest covers all platforms. Pinning that index digest keeps linux/amd64 and linux/arm64 builds consistent.
Avoid pinning a single platform's child manifest unless you build for exactly one architecture.
Gotchas
Pinning and then never rebuilding. The most common failure. The image is reproducible and quietly months behind on patches. Schedule a weekly rebuild or nightly Renovate run.
Copying a digest by hand and typo-ing it. A wrong hash fails the pull with a manifest error. Always resolve it with docker inspect or let Renovate write it.
Pinning a platform manifest instead of the index. Cross-arch builds break or silently pull the wrong platform. Pin the index digest that the tag resolves to.
Assuming the digest survives a re-push. If you mirror images into a private registry, the digest is preserved only if you copy by digest (for example with crane cp), not by re-tagging.
Forgetting the readable comment. A bare @sha256: line is unreviewable. Keep the tag and date in a # comment above it.
Alternatives
Floating minor tag (python:3.13). Simple and auto-patches, but non-reproducible and can break builds without warning. Fine for local dev, weak for production.
Digest pin plus Renovate (recommended). Reproducible and patched, at the cost of a bot config and a steady stream of small PRs to review and merge.
Vendored base in a private registry. Mirror an approved, pre-scanned base and pin its digest. Best for regulated environments; adds a mirroring pipeline to maintain.
Rebuild-from-source bases (for example Wolfi/Chainguard). Frequently rebuilt minimal bases with few CVEs. Pin their digests too and rely on their fast patch cadence.
FAQs
Does pinning by digest make my image more secure? Not by itself. It makes builds reproducible. Security comes from rebuilding onto patched digests on a cadence.
How do I find the digest for a tag?
Pull the image and read RepoDigests with docker inspect, or query the registry with a tool like crane digest.
How often should I bump digests? Weekly is a common baseline, with immediate bumps when a Critical CVE in the base is disclosed.
Should application images also be pinned? Yes, when one image bases on another internal image. Pin that internal base by digest for the same reproducibility guarantee.
Can admission control require digests?
Yes. A ValidatingAdmissionPolicy can reject any pod whose image reference lacks @sha256:, forcing digests cluster-wide.
Does this affect the node runtime? Only indirectly. You build and pin with Docker, and containerd via the CRI pulls the pinned digest when it runs the pod.
Related
- The Container Vulnerability Lifecycle
- Vulnerability Basics
- Minimal Attack Surface
- Exception Process
- Image Policy Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).