Tags, digests & pinning
Mutable names vs immutable content.
A tag is a human-friendly, mutable pointer: payments-api:1.4.2 names some content today and can be repointed at different bytes tomorrow. A digest is the content itself — the SHA-256 of the image manifest, written name@sha256:… — and it is immutable by definition. Humans read and push tags; systems that care about running exactly what was tested resolve and deploy by digest.
$ docker buildx imagetools inspect registry.internal/payments-api:1.4.2Name: registry.internal/payments-api:1.4.2Digest: sha256:9f2a44c1e0b7...$ docker images --digests registry.internal/payments-apiREPOSITORY TAG DIGEST IMAGE IDregistry.internal/payments-api 1.4.2 sha256:9f2a44c1... a1b2c3d4
Scenario: two nodes, same tag, different code
A service misbehaves on one node but works on another, both “running 1.4.2.” The tag was rebuilt and re-pushed between the two pulls, so the nodes hold different bytes under the same name — an unversioned, invisible deploy. Pinning by digest eliminates this class of bug entirely: the digest is the bytes, so every pull on every machine forever is identical.
# a tag for humans to read, a digest for the machine to actually runimage: registry.internal/payments-api:1.4.2@sha256:9f2a44c1e0b7...# readable ─────────────────────┘ └── the exact bytes, immutable# resolve the current digest for a tag before deploying:# docker buildx imagetools inspect <img>:<tag> | grep Digest
Scenario: a base image changed under your build
A nightly rebuild that always “worked” suddenly ships a new CVE, because FROM node:22-alpine floats — the tag now points at a rebuilt base. Pin the base by digest so rebuilds are reproducible, and bump it deliberately when you have reviewed the change. The same discipline that protects your own images protects the shoulders they stand on.
# floating: today’s node:22-alpine may not be tomorrow’sFROM node:22-alpine# pinned: the exact base bytes, rebuild-stable, bumped on reviewFROM node:22-alpine@sha256:d71f4f2a9c...