Build once, and let the artifact be the unit that moves
Independent builds can differ even at the same commit. Promoting one artifact by digest makes it clear which bytes were tested, while configuration and runtime differences still need validation.
TL;DR: One build produces one artifact, addressed by content digest. That exact artifact moves through environments, and configuration is injected rather than baked in. A different build does not automatically inherit artifact-specific validation. Reproducible builds can produce identical digests; verify that equality rather than assuming it.
The symptom this explains
A change passes every test in staging. It is promoted to production, where it fails. Somebody checks and confirms that production is running the same commit, so the investigation turns to the environment: configuration, data, load, a difference in the cluster.
Sometimes that is where the answer is. Often the premise is wrong, because the same commit was built twice and the two builds are not the same thing. Establishing which of those you are in takes one comparison, and a pipeline that rebuilds per environment cannot make it.
What rebuilding actually costs
Two builds of the same commit are not guaranteed to produce the same artifact, and the reasons are ordinary rather than exotic. A dependency range resolved to a newer patch release that was published between the two runs. A base image tag that moved, so FROM pulled different bytes. A build tool present at a different version on a different runner. A timestamp or a build number compiled into the binary, which changes the artifact even when nothing else did.
A changed dependency can alter runtime behavior even when the application source is unchanged. When production fails after staging passed, compare the artifact identities before treating the environments as equivalent. The source revision identifies only part of the build input.
Promoting the tested digest removes the separate environment build from that comparison. You still need to establish that production pulled the intended artifact and that its runtime configuration is compatible.
Address by digest, not by tag
A tag is a mutable pointer. myapp:1.4.2 today and myapp:1.4.2 next month may be different bytes, and latest may move frequently.
A digest is the content. Deploying by digest means the manifest names exactly what will run, a rollback names exactly what ran before, and a signature over the artifact continues to mean something. Artifact-specific attestations refer to the identified content. Verification must also establish that the signer or builder is trusted under your policy; a digest alone says nothing about who produced the artifact.
Tags remain useful as human labels. Prefer digests in deployment records so moving a tag cannot silently change the intended content.
Configuration is injected, never baked
The corollary that teams get wrong. If the artifact contains the staging database URL, it cannot be the production artifact, and you are back to building per environment through the side door.
So configuration arrives at runtime: environment variables, a mounted file, a config service. The artifact is identical everywhere and its behaviour differs because its inputs differ. That is also what makes a promotion a pointer change rather than a build.
Secrets follow the same rule with more force. A secret baked into an image may remain in a layer, build history or cache even after a later layer deletes it. Anyone with access to that material may recover it. Revoke the exposed credential, clean up retained copies under policy, and rebuild without embedding its replacement.
What promotion becomes
An artifact that has been built once moves by having its digest referenced in the next environment's manifest. That is a small, reviewable diff, it is auditable, and it is reversible by referencing the previous digest.
The chain that follows is worth stating: because the artifact is immutable, the tests that ran against it stay meaningful; because it is addressed by digest, the deployment record is exact; because configuration is external, the same artifact can be the one that was validated. Each property depends on the one before it.
Where it gets hard
Compiled-in configuration in some frameworks, where the build genuinely needs an environment value. Fix it in the framework or accept per-environment builds for that component with the trade written down.
Multi-architecture images, where one tag points at an index of several digests. Record the index digest and the platform-specific image identity. The index can select different images for amd64 and arm64, so a staging test on one architecture does not establish behavior on the other.
Long-lived artifacts. An artifact deployed for eighteen months contains dependencies with vulnerabilities disclosed since. Immutability is not an argument against rebuilding on a schedule; it is an argument that a rebuild is a new artifact which goes through the pipeline again rather than being patched in place.
Separate identity from approval
An illustrative release has three independently recorded facts: image digest A, configuration revision 12 and test report T. Report T shows that A passed a booking test using configuration 12. Production proposes A with configuration 13, which points at a different database.
The artifact identity matches. The database identity, schema and access permissions still need verification. Recording the configuration change beside A makes that remaining work visible instead of treating “same image” as a complete release approval.
| Record | What it establishes | What remains unknown |
|---|---|---|
| Content digest | Which artifact content is referenced | Whether it behaves correctly |
| Verified provenance | Recorded origin and build facts under a trust policy | Whether the test coverage is sufficient |
| Test report tied to the digest | Results for the exercised inputs and environment | Behavior under different production conditions |
| Release record | Artifact and configuration selected for deployment | Whether the running workload matches the selection |
The SLSA provenance specification describes build-origin evidence. Keep that evidence separate from application test results. For a full approval exercise, work through promoting the tested artifact.
Ask whether the tested bytes are the running bytes
That is the whole question, and it has only three answers. Yes, because the deployment names a digest that appears in the test record. No, because the pipeline rebuilds per environment, in which case the test result applies to a different artifact and you should stop treating it as evidence about this one. Or "probably", which means the pipeline is reporting a conclusion it has no way to reach, and that third answer is the common one.
Self-check
A release fails in production after passing in staging. The deployment manifests for both environments reference myapp:2.7.0, the pipeline builds the image once per environment from the same commit, and the staging test report records that commit. Separately, a colleague argues that because both environments built the same commit, the artifacts are necessarily identical and the cause must be environmental. Decide what you can establish, what you cannot, and what single change would have made the question answerable. Then say whether two independently produced images having the same digest tells you anything. Work it through before reading on.
What you can establish is narrow: the same source revision went into both builds. That is genuinely less than it sounds, because the source revision is one of several inputs. The tag 2.7.0 establishes nothing further, since a tag is a mutable pointer and both manifests naming it is consistent with them running different bytes.
What you cannot establish is that the artifacts match, and the colleague's argument has the logic backwards. Identical commits do not imply identical artifacts; that implication needs reproducible builds, which is a property you verify rather than assume. So the environmental hypothesis may well be right, and nothing available here supports preferring it over a build difference. You can still resolve it, by comparing the two images' digests directly, which is the first thing to do and takes seconds.
The change that would have made it answerable is to build once and promote that artifact by digest, so the production manifest names the exact content the staging test exercised. Then the same failure carries real information: the bytes are proven identical, and the cause is in configuration, data or environment by elimination rather than by assumption.
On the digest question, the direction of the inference is what matters. Digest equality does establish identical content, because a digest is computed from the bytes; two independently produced images with the same verified digest do contain the same thing, which is what reproducible builds are for. The error is the reverse move, inferring equality of content from equality of a commit or a tag. And even with matching digests, the environment-specific configuration those bytes are given has not been tested by anything.
Next: immutability and replacement applies the same reasoning to infrastructure rather than artifacts, and promoting the tested artifact works a full approval through as an exercise.
When a release contains several stored objects, object consistency and versioning explains how an immutable manifest keeps readers on one complete release.
Pipeline caching and reproducibility examines the inputs and trust boundaries needed to keep a warm build consistent with a clean one.