Pinning a dependency to something that can't change underneath you is the same idea whether it's a CI action or a container base image. We already measured how often popular projects pin their GitHub Actions to a commit SHA. So we asked the identical question one layer down: how often do they pin their Docker base images to an immutable @sha256 digest?
TL;DR
- Across 25 popular infra/app projects (250 external base-image references), only 7.6% are pinned to an immutable
@sha256digest (19 refs). -
81.6% use a mutable version tag like
node:20.1.2(204 refs); 10.8% are:latest/untagged (27 refs). - Only 5 of 22 projects that ship Dockerfiles use
@sha256pinning for any base image. - Same supply-chain principle, measured before: GitHub Actions pinned to a commit SHA = 67.6%. That's roughly 9× more adoption for Actions than for container digests.
- Point-in-time (2026-07-21), reproducible across all 25 repos with public tooling.
The numbers
| Pin type | Refs | Share |
|---|---|---|
Digest (@sha256, immutable) |
19 | 7.6% |
Version tag (node:20.1.2) |
204 | 81.6% |
:latest or untagged |
27 | 10.8% |
| Total external base images | 250 | 100% |
The 5 projects that digest-pin any base image: posthog, Ghost, elasticsearch, moby, n8n.
Side by side with the Actions study:
| Immutability practice | Adoption across popular repos |
|---|---|
| GitHub Actions pinned to a commit SHA | 67.6% |
| Docker base images pinned to a digest | 7.6% |
Why it matters
FROM node:20 (or worse, FROM node:latest) pulls whatever image that tag resolves to at build time. Tags are mutable — the same node:20 can be a different image tomorrow — so a reproducible, tamper-evident build pins to the content digest: FROM node@sha256:…. It's the exact same risk that made SHA-pinning a CI checklist item after the 2025 GitHub Actions compromises (tj-actions, reviewdog) — just one layer down, and adopted ~9× less often.
Our read on the gap: the Actions ecosystem had a forcing function (those compromises) plus a one-click habit (Dependabot). The base-image equivalent hasn't had its forcing function yet — and digest strings are ugly to write by hand, so most teams stop at a version tag.
A fair caveat
Version tags are not "wrong." node:20.1.2 is readable, gets patch updates, and is fine for most teams. Only a @sha256 digest is truly immutable, so we report it as the strict gold standard for reproducible/tamper-evident builds — not as a claim that everyone on a version tag is insecure. The clearly-risky category is :latest/untagged, which makes builds non-reproducible and lets the base change silently. Some pinned/unpinned images are also a project's own first-party images, where the trust model differs.
How to pin
# mutable — resolves at build time, can change underneath you
FROM python:3.13-slim
# immutable — the exact image content, human-readable tag kept in a comment
FROM python:3.13.13-slim-bookworm@sha256:355bfa66770995d7e9a0da4b3473b44d0cb451f6b56f5615ad9c39e3c4eca03f
Then let Dependabot (package-ecosystem: docker) bump the digest so you stay patched without tracking a moving tag.
How we measured it
- Corpus: 25 popular infra/app repos; 22 ship Dockerfiles with external base images.
-
Per repo: sparse-checkout the Dockerfiles, parse every external base-image
FROM(excluding internal stage aliases,--platformflags, build-args, andscratch), and classify as digest, version tag, or:latest/untagged. -
Cross-check: Shield's
CONTAINER-002(:latest) rule against an independentFROMparser — the:latestcounts agreed (16 vs 15, one build-stage edge case). Digest classification is unambiguous (a reference either contains@sha256:or it doesn't), and we hand-verified samples at both ends.
Reproduce it yourself:
git clone --depth 1 --filter=blob:none --sparse https://github.com/grafana/grafana g
cd g && git sparse-checkout init --no-cone && git sparse-checkout set '**/Dockerfile' && git checkout
# a base image is immutable only if the ref contains @sha256:
grep -rhE '^\s*FROM\s' --include='*Dockerfile*' . | grep -v '@sha256:'
Limitations
- 25 popular repos, point-in-time (2026-07-21) — not a random sample; large projects skew more disciplined, so the ecosystem-wide digest rate is plausibly lower.
- We classify references, not unique images; a reused base weights a repo's count.
- Stage aliases,
--platformflags and build-args are excluded; one residual cross-file alias (~0.4%) may remain and doesn't move the headline.
Full per-repo dataset, method, and the cross-study comparison are on the canonical page: Do popular projects pin their base images? We checked 25. Zennoxa Research publishes reproducible security data on public projects with public tooling — no third-party scanner involved.
Top comments (0)