Zennoxa Shield

Do popular projects pin their base images? We checked 25

Popular projects SHA-pin their GitHub Actions two-thirds of the time — but pin their Docker base images to an immutable digest only 7.6% of the time. Same supply-chain idea, a fraction of the adoption. Reproducible data across 25 repos.

July 21, 2026By Zennoxa Researchsupply-chaindockercontainersdevsecops

TL;DR — Across 25 popular infrastructure and application projects (250 external base-image references), only 7.6% are pinned to an immutable @sha256 digest. 92.4% use a mutable reference. Compare that to our GitHub Actions pinning study, where 67.6% of action references were SHA-pinned: it's the exact same supply-chain idea — pin to something that can't change underneath you — adopted ~9× less often for containers than for CI actions.

The same problem, one layer down

We already looked at GitHub Actions pinning: a uses: action@v4 runs whatever that mutable tag points at today, so the hardened practice is to pin to an immutable commit SHA. A Docker base image is the identical risk one layer down. 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:….

So we asked the same question of containers that we asked of Actions: how often do popular projects actually pin?

What we measured

  • 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, --platform flags, build-args, and scratch), and classify the reference as digest (@sha256:…, immutable), version tag (node:20.1.2, mutable), or :latest/untagged (most mutable).
  • Tool: Shield's CONTAINER-002 (:latest) cross-checked against an independent FROM parser; the :latest counts 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 (posthog's python@sha256:355b… vs grafana's golang:latest).

Full per-repo dataset: data.json.

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%
  • 7.6% digest-pinned. Only 5 of 22 projects use @sha256 pinning for any base image (posthog, Ghost, elasticsearch, moby, n8n).
  • 81.6% version tags. The bulk of the ecosystem sits here — reasonable and readable, but still mutable (a tag can be repushed).
  • 10.8% :latest/untagged — the genuinely non-reproducible end (FROM golang:latest, FROM busybox).

The cross-study point

Put the two studies side by side:

Immutability practice Adoption across popular repos
GitHub Actions pinned to a commit SHA 67.6%
Docker base images pinned to a digest 7.6%

Same principle, ~9× the adoption for Actions. Our read: the GitHub Actions compromises of 2025 (tj-actions, reviewdog) put SHA-pinning on every CI checklist, and Dependabot made it a one-click habit. 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. Note too that some pinned/unpinned images are 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) or a tool like pin-github-action's container equivalent bump the digest so you stay patched without tracking a moving tag. Shield flags :latest base images (CONTAINER-002) today.

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, --platform flags and build-args are excluded; one residual cross-file alias (~0.4%) may remain and doesn't move the headline.

Reproduce it

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:'

Per-repo counts and method in data.json.


Zennoxa Research publishes reproducible security data on public projects with public tooling. No third-party scanner is involved.

Shield flags Dockerfiles that pull a mutable base tag (:latest, :edge) instead of a pinned one — the exact gap this study measured.

See what Shield finds in your code

Free to start, no credit card. One pass covers SAST, secrets, vulnerable dependencies, containers and IaC — ranked by what’s actually reachable, not just severity.

Scan your repo free

Prefer the terminal? The offline CLI runs with no account

More research
Do popular projects pin their base images? We checked 25 — Zennoxa Shield