DevOps · Release Engineering
Immutable Artifact Coordinates and Why Digests Matter
How a version-plus-repository name identifies a byte string, what breaks when the mapping can change, and how attestations bind a build input set to the digest that was published.
A version number is a name, and names are only useful if they resolve to the same thing every time they are used. Every artifact distribution channel is therefore a mapping from names to byte strings, and the entire reproducibility story of a release depends on that mapping being a function rather than a relation: one name, one value, forever.
Most registries are not built that way by default. They are built as storage with a metadata layer, and the metadata layer usually permits a name to be re-associated with different bytes. The audit question for any release process is where that permission exists and what sits in front of it.
What a coordinate has to identify
The conventional coordinate is a tuple: registry host, repository namespace, artifact name, and version or tag. registry.example.com/team/service:1.4.2 names a manifest, which names a set of blobs. Each of those steps is a lookup, and each lookup is a place where the answer can change.
Content addressing sidesteps the chain by making the digest the identifier. A digest is computed over the bytes and, for a container image, over the manifest that lists the layer digests. Pulling by digest removes the last lookup, because the client verifies that the received content hashes to the requested value; a mismatch is a hard failure rather than a silent substitution. The property this gives is not “trustworthy” but “self-consistent”: the client knows it received exactly the bytes it asked for, and needs to know separately that the digest it asked for is the digest that was built.
That second question is the one digests cannot answer. A digest proves identity, not origin. An attacker who can replace both the artifact and the digest in every place a client reads can give a client a coherent, verified, and entirely wrong artifact.
The hash is the algorithm, and it ages
Digest strings are algorithm-prefixed for a reason. sha256: in front of the hex is not decoration; it declares which function produced the value, and the specification requires the prefix so that a verifier knows what to compute. FIPS 180-4 defines the SHA-2 family, and SHA-256 is the common default: 256 bits, a 64-character hex string, a 32-byte binary value.
Shorter digests exist in practice. Truncated hashes of a few bytes are adequate for content-addressed storage deduplication, where the store verifies on collision. They are not adequate as a security boundary, since a collision search scales with the square root of the output space. The relevant numbers: a 256-bit digest gives a generic collision search of about 2^128 work, which is not feasible; a 64-bit digest gives about 2^32, which is minutes on one machine. Anywhere a digest is used to decide whether to install code, the length has to be in the second category.
Algorithm agility is the corollary. Because the prefix is part of the value, a repository can hold both a sha256: and a sha512: reference to the same blob, and a client can be updated to require the stronger one without re-publishing. The alternative, treating the algorithm as configured state, means a client and a server can disagree about what a bare hex string means.
Signatures bind a key, not a build
A signature over a digest says that whoever holds the key approved those bytes. That is a different claim from “these bytes came from this source revision”. The gap between them is where most supply-chain reasoning goes wrong: signing is necessary and insufficient.
What closes the gap is an attestation that records the build. The shape is: the digest of the artifact, the digest of each input, the identity of the build system, and the invocation parameters, all signed. in-toto formalises the chain as a sequence of link metadata files with an expected command sequence, so a verifier can check not only that the signer is trusted but that a build step of the right kind ran with the right inputs. The Update Framework takes the complementary angle on distribution: separate, independently keyed roles for the root of trust, targets, snapshot, and timestamp, so that compromising one key does not let an attacker serve arbitrary content indefinitely. TUF’s timestamp role exists specifically to bound the staleness window, which addresses the rollback attack that plain signature verification cannot detect.
For container images, the manifest’s layer digests plus an attached, signed attestation naming the base image digest and the build definition is the practical form. The verification is then a comparison: does the attestation’s subject digest equal the digest being deployed, and does the attestation’s builder identity match an expected value, and does the recorded base image digest match the pinned one?
A useful invariant falls out of this: two builds from identical inputs should produce identical digests. Where they do not, the input set is incomplete, because something unrecorded varied. That is a diagnostic, not a guarantee — a build can be perfectly reproducible and still wrong — but it is cheap to check and it detects the common case of an unpinned toolchain.
Where mutability is reintroduced
Four places re-introduce a mutable name in front of an immutable digest, and each is a decision to accept the risk rather than evidence of carelessness.
Tag movement. A tag is a pointer. Moving latest or a minor tag in place is the publishing equivalent of re-using a version, and it is safe only for consumers who never resolve by tag. The remedy is that consumers deploy by digest, and the tag exists for humans reading a list.
Overwrite-capable registries. Some artifact stores permit re-upload under the same coordinate. Where that is permitted, the version is a name whose meaning can change after review, and no downstream hash comparison helps because the hash was computed from whatever was there when the lockfile was written. Immutable-tag policies or an external mirror with write-once semantics are the mitigations.
Re-tagging during promotion. A pipeline that copies an image by pulling a tag and pushing to a new repository by tag creates a new manifest, which has a new digest even when the layers are identical. Promotion should preserve the manifest and the digest, or record the new digest explicitly in the release record.
Locally rebuilt images. Building on the deployment host for a “minor” configuration change produces an artifact whose inputs are whatever that host had. The digest differs; the tag may be identical. This is the failure that makes an immutable coordinate worth insisting on, because it is invisible in every view that shows only tags.
The release record as the join table
What ties it together is a record, written at release time, that maps the human coordinate to the digest and to the evidence: the source revision, the build system identity, the attestation digest, the verification results. The record is what a later audit reads, because the registry only knows the current state of its names and the repository only knows the commits.
The record’s schema matters less than its two properties: it is append-only, and it is identified by the same digest the deployment consumes. A record keyed on a version number is worthless once the number is reused. A record keyed on a digest is valid for exactly one artifact.
There is a natural connection back to versioning here. The promise that a version number denotes one artifact is exactly the promise that the coordinate is immutable, and the same rule that forbids re-publishing a version is what lets a lockfile, a changelog, and a vulnerability report agree with each other about which artifact they describe. The constraints a consumer writes against those numbers assume this; the constraint mechanics are covered in lockfiles and version resolution, and the promotion mechanics that must preserve digests in pre-release channels and promotion in package registries.
Limits
Immutability of the artifact does not imply immutability of its dependencies, its runtime, or its configuration. A digest-pinned image running against a mutable database, resolving DNS that changes, or reading configuration from a live store is only partly reproducible, and the boundary between what the digest covers and what it does not is the boundary of the guarantee. The one-line version: a digest tells what was deployed, never where it ran.
References
- RFC 9110: HTTP Semantics — IETF
- FIPS PUB 180-4: Secure Hash Standard — NIST
- The Update Framework specification — TUF project
- in-toto: Providing farm-to-table integrity for open-source software — Torres-Arias et al., Transactions on Dependable and Secure Computing
- Open Container Initiative — Image Spec — Open Container Initiative