← Back to Docker Base Image Pin Check
Skimming a Dockerfile for an obvious FROM node or FROM node:latest
genuinely works, and it is the method most people already use. This page names no product or
company being compared against: "reading the Dockerfile by hand" is a generic manual habit, not
a competing tool.
| Case | Reading the Dockerfile by hand | This tool |
|---|---|---|
Bare FROM node or explicit FROM node:latest | Correctly caught by a reviewer who knows to look for it | Correctly caught the same way |
A specific-looking tag, FROM node:18 | Reads as already careful -- easy to not flag, even though the tag can still be repointed | Classifies any non-digest tag as mutable, specific-looking or not |
A later FROM builder referencing an earlier AS builder stage | Requires remembering every stage alias defined earlier in a long file to avoid a false alarm | Tracks every AS <name> alias while scanning and checks later FROM lines against that running set automatically |
A private registry reference with a port, myregistry.local:5000/node:18 | Easy to misread the port's colon as the tag delimiter | Always locates the tag inside the last /-separated path segment, never the host |
Multiple Dockerfiles across a repository (Dockerfile, Dockerfile.prod, worker.dockerfile) | Coverage depends on remembering every file exists | CLI discovers every matching filename under a directory on its own |
node:18 is the trap this tool exists to catch. It reads as more careful than bare
node or node:latest, and it is not pinned in the sense that matters --
Docker still resolves 18 to whatever bytes that tag points to right now, and unlike
a version bump you would review, a later push to the same tag is silent. A reviewer scanning for
"obviously risky" names has no reason to single this out. This tool draws no such distinction:
anything short of a content digest classifies as mutable, regardless of how specific the tag looks.
A multi-stage Dockerfile reuses names for its own internal stages, and a later
FROM <name> that refers to one of them is not a registry pull at all -- it has
no tag to pin. Getting this right by eye means holding every AS <name> alias
defined earlier in the file in mind while reading each subsequent FROM line, which
gets harder as a Dockerfile grows more stages. This tool tracks the alias set mechanically as it
scans, so the distinction does not depend on what a reader remembered from forty lines up.
This tool answers one question -- is this FROM line pinned to a content digest --
and stops there. It does not resolve a mutable tag to a digest for you (it prints the
docker buildx imagetools inspect command instead), and it makes no claim about
whether the code inside a pinned image is itself safe. Deciding whether to trust a given digest,
and reviewing what a base image actually contains, is still a job for you.
A reviewer who knows to look for it can spot the obvious case immediately: FROM node with no tag at all, or an explicit FROM node:latest. Both read as risky on sight, because latest is a well-known name for "whatever the publisher pushed most recently." That review genuinely works for the obvious case -- the same case this tool's classify.mjs also flags.
node:18 reads as more careful than bare node or node:latest, because it names a specific major version -- but Docker still resolves :18 to whatever bytes that tag currently points to, and the publisher can push a new build to it at any time with no version bump to review. A reviewer scanning for the word "latest" or a missing tag has no reason to flag node:18, since nothing about it looks wrong. This tool draws no such distinction: every tag, specific-looking or not, that isn't a content digest classifies as mutable.
A multi-stage Dockerfile can define FROM golang:1.22 AS builder early on and then write FROM builder forty lines later to continue that stage -- and FROM builder is not a registry pull at all, so it has no tag to pin and nothing to flag. A reviewer working through a long Dockerfile has to remember every stage alias defined earlier in the file to make that call correctly; forgetting one risks either a false alarm on a name that is not a registry image, or missing that a later re-use of the same word is a genuine external FROM. This tool tracks every AS <name> alias as it scans and checks each later FROM against that running set automatically, so the distinction never depends on what a reader kept in their head.
A reference like myregistry.local:5000/node:18 contains two colons, and naively splitting on the first one reads :5000/node as the tag -- nonsense that hides the real tag, 18. The actual grammar is that a colon only starts a tag inside the last slash-separated path segment; the colon in registry:5000/ is part of the host, not a tag delimiter. Getting this right by eye on an unfamiliar private-registry reference is easy to get backwards. This tool's splitNameAndRef always locates the tag inside the last path segment only, so a registry port is never misread as one.
No, but it removes the tedium of doing so by hand. A repository can carry a Dockerfile, a Dockerfile.prod, and a worker.dockerfile, each with its own FROM lines, and a manual review has to remember to open every one, every time a new file is added. The CLI (python3 check.py) discovers Dockerfile, Dockerfile.*, and *.dockerfile anywhere under a given directory on its own, so coverage does not depend on a reviewer's memory of which files exist.