check-images.py#
Reads agent/catalog.json and answers three questions about
the images in it: are the references well formed, do they resolve at the registry, and
are any of them pinned to a digest. It can also do the pinning.
scripts/check-images.py agent/catalog.json --offline # shape only, no network
scripts/check-images.py agent/catalog.json # + reachability
scripts/check-images.py agent/catalog.json --require-digest # fail unless all are pinned
scripts/check-images.py agent/catalog.json --pin # resolve tags, rewrite the fileStandard library only, no registry credentials, and no dependency on a container runtime.
Options#
| Option | Meaning |
|---|---|
catalog | The catalogue file. Required. |
--offline | Shape check only. Never touches the network. |
--require-digest | Exit non-zero unless every image is pinned by digest. |
--pin | Resolve each tag to a digest and rewrite the catalogue. Needs the network; refused with --offline. |
Builtin entries and entries with no image are skipped everywhere.
Exit 0 when nothing is wrong, 1 otherwise.
Why pinning matters here#
An app install is sold as a change you review and can roll back. A floating :latest
breaks that promise quietly: the diff you approved names a tag, and the bytes behind that
tag change without a commit, a release or an audit entry. name:tag@sha256:… keeps the
tag for humans and makes the bytes part of the reviewed change.
The shape check#
The reference grammar is the one the router's container runtime accepts, and the one the agent stages verbatim:
host/repo[:tag][@sha256:<64 hex>]| Complaint | Means |
|---|---|
not a fully-qualified reference | It does not match the grammar at all. |
host 'x' does not look like a registry | No dot and no port in the host part. |
registry named twice in the reference | The repository starts with a registry name — docker.io/docker.io/…. |
no tag and no digest | Nothing says which version. |
--offline is the check CI can run on every push: no network, no credentials, so it
cannot flake, and it catches the malformed references that used to reach the catalogue.
The reachability check#
Without --offline, each image is HEADed at its registry — anonymously, eight at a
time. It fetches a pull token (docker.io, ghcr.io and lscr.io all speak the same
token endpoint), then requests the manifest, accepting both Docker and OCI manifest and
index types.
ok docker.io/adguard/adguardhome:latest
BAD ghcr.io/example/thing:latest HTTP 404A digest, when present, is the authoritative reference; the tag is decoration.
The summary#
Every run ends with a count:
36 image(s); 0 pinned by digest, 36 floating.
Floating tags mean an install is not reproducible and not reviewable. Pin them with
--pin, then make --require-digest a gating check.
adguard: docker.io/adguard/adguardhome:latest
…Floating tags are not an error by default. Pinning is a catalogue change somebody has
to review, and failing every build until it lands would only teach people to ignore the
output. --require-digest is how you make it a gate once it has landed.
--pin#
Resolves each unpinned image's tag to the digest the registry currently serves and rewrites the catalogue in place:
pin adguard docker.io/adguard/adguardhome:latest -> docker.io/adguard/adguardhome:latest@sha256:…
rewrote agent/catalog.json: 36 image(s) pinned. Read the diff, then run `go test ./...` in agent/.The tag is kept alongside the digest, so the entry is still readable by a person. An image whose digest cannot be resolved is reported and left alone.
It is a deliberate, online, human-run step that produces a diff somebody reads — deliberately not something CI does on its own.
Running it#
# Every push, in CI:
python3 scripts/check-images.py agent/catalog.json --offline
# Occasionally, by hand:
python3 scripts/check-images.py agent/catalog.jsonSee also#
- Catalogue schema — the
imagefield and how it is validated in the agent. - The catalogue — every image, as it stands.
- Tools
- Apps are configuration
Checked against#
scripts/check-images.py (REF, parse, lint,
head_manifest, images_of, main),
agent/catalog.json,
agent/apps.go (imageRef, validImageRef),
docs/apps.md "Image pinning",
README.md.