The build artifact management decision record: why the versioning model you configured determines your build reproducibility surface and your supply chain integrity gap

Artifact versioning, retention policy, and signing are build infrastructure decisions that are almost never recorded explicitly — they emerge from the defaults of the first CI configuration and accumulate their consequences as the product, team, and security posture grow. Three failure patterns: the team whose incident retrospective required a forensic diff between the production artifact and a rebuilt version from the same commit — and the diff was non-empty because a dynamically resolved build dependency had advanced in the intervening week; the team whose rollback was blocked because artifact storage had been pruned to cut costs, with no documented retention policy that protected the version they needed; and the team whose container base image was silently replaced with a compromised version because the CI pipeline pinned by mutable tag rather than immutable digest, and the substitution ran undetected in production for nineteen days.

A 31-person SaaS company built a data pipeline integration product — a service that extracted records from enterprise ERP systems, transformed them into a normalized schema, and loaded them into customers' data warehouses. The product processed payroll data, financial records, and inventory transactions for mid-market manufacturing companies. The engineering team ran CI on GitHub Actions and pushed build artifacts to a private container registry after every merge to main. The Dockerfile specified FROM python:3.11-slim. The Python dependency file used minimum version constraints for most packages and a few pinned versions for libraries that had previously caused compatibility problems. The build worked. It had always worked. Nobody had ever needed to do anything other than build and deploy.

In the company's third year, a production incident caused a partial pipeline failure for three customers over a four-hour window. The incident was resolved by restarting the affected service pods, which reloaded the current artifact. The root cause was traced to an unexpected behavior change in a data transformation library. The post-incident review required the team to determine whether the behavior change was present in the version that had been running in production at the time of the incident — a question that mattered because three customers had received partially transformed data and the team needed to identify the exact window during which the incorrect transformation was applied.

The team pulled the container image digest from the production pod logs and found the registry tag. The tag resolved to the most recent build, not the build that had been running during the incident — the image had been rebuilt and redeployed after the incident as part of the recovery process, and the original image from the incident window was no longer the current version associated with that tag. The team rebuilt from the commit SHA that had been tagged for the incident-era deploy. The rebuild produced an artifact. The team compared the behavior of the rebuilt artifact against the incident reports and found a discrepancy: the transformation behavior in the rebuild did not match the behavior described in the incident reports. A rebuild from the same commit had produced a different artifact.

The investigation required two days. The cause was a transitive dependency of the transformation library that was resolved at build time from PyPI without a pinned version. The original build had resolved charset-normalizer 3.3.1. The rebuild, run nine days later, resolved charset-normalizer 3.3.2, which included a behavior change in the encoding detection path that affected the transformation output for a specific subset of multi-byte character sequences in the ERP data. The team had not known that charset-normalizer was in the dependency graph at all — it was a transitive dependency three levels deep, resolved dynamically by the direct dependency's own install process. The incident window, the scope of affected customers, and the specific records requiring remediation could not be determined from the rebuild. They were inferred from the application logs, which had enough detail to reconstruct the approximate scope but not enough to produce a precise record count with confidence. The forensic reconstruction that would have taken two hours with a bit-identical artifact took two days without one, and the confidence in the result was lower. The build process had never been discussed as a decision. It was the configuration that came out of the initial CI setup session, and it had worked every day until the day it mattered that a rebuild produce the same artifact as the original.

A 38-person SaaS company built a developer productivity platform — a tool for coordinating code reviews, tracking technical debt items, and surfacing engineering metrics for team leads. The platform was deployed as a set of containerized services on a managed Kubernetes cluster. Artifacts were built in CI, pushed to a container registry with semantic version tags and a rolling latest tag, and deployed via a GitOps pipeline that updated the tag reference in the deployment manifests on each merge to main.

In the company's second year, a feature release introduced a regression in the pull request notification system that caused duplicate notifications to be sent to users for a subset of events. The regression was detected two hours after the deploy when a batch of user support tickets arrived. The incident response team initiated a rollback. The rollback procedure in the runbook called for redeploying the previous version tag — the tag that had been current before the failed deploy. The team opened the container registry console to locate the previous version image.

The image was not there. The registry contained twelve images, covering the last twelve builds. The problematic deploy was the thirteenth build since the last registry pruning event, and the pruning script had run two nights earlier, deleting images older than the most recent ten versions. The previous version — the stable version the team needed to redeploy — was the eleventh most recent build at the time the pruning script ran. It had been deleted along with builds two through eleven. The team's rollback path was a rebuild from the commit SHA of the previous stable release.

The rebuild took thirty-one minutes. During those thirty-one minutes, duplicate notifications continued to be sent. By the time the rebuild completed, deployed, and the cluster finished its rolling update, the incident had run for two hours and forty minutes, approximately two hours and ten minutes longer than it would have if the artifact had been available in the registry. The engineering manager reviewed the pruning script configuration afterward and found that the retention setting — keep the last ten versions — had been set during a cost optimization sprint eight months earlier. The sprint had targeted three cloud cost categories; artifact storage was one of them. The engineer who had set the retention value had chosen ten as a round number that seemed like enough coverage, without reviewing the incident response runbook or checking how often rollbacks occurred. The incident response runbook had been written before the cost optimization sprint and assumed that any recent release would be available in the registry for immediate redeployment. Neither document referenced the other. The retention policy had never been discussed as a decision with an explicit tradeoff: how many versions to retain, against what cost, to guarantee what rollback window. It was a configuration value set during a cost sprint that shortened an unrelated incident by two hours and ten minutes eight months later.

A 45-person SaaS company built a workflow automation platform — a no-code tool for connecting enterprise SaaS applications through trigger-and-action pipelines. The platform ran customer workflow executions in isolated container sandboxes, one sandbox per workflow execution, spawned and destroyed on demand. The sandbox base image was pulled from Docker Hub using the tag node:20-alpine. The pipeline pinned to this tag in the Dockerfile, and the Dockerfile was reviewed as part of the normal code review process. Nobody had raised a concern about the tag.

In a three-week window, a supply chain research group discovered that a widely-used package in the Node.js ecosystem had been compromised via a maintainer account takeover. The compromised version included a payload that exfiltrated environment variables from the execution context to an external endpoint on first startup. The payload was subtle — it ran once, exfiltrated, and did not persist, so behavioral anomalies in individual containers were not obvious in the logs. The compromised package version was incorporated into the node:20-alpine base image in an update that Docker Hub published under the existing tag without changing the tag name. The compromise was present in the tag from the day of the update forward.

The 45-person company's CI pipeline pulled node:20-alpine during its next build run, two days after the update. The pulled image included the compromised package. The resulting container artifact was pushed to the company's private registry and deployed to staging. The staging environment ran the artifact for three days of testing before it was promoted to production. The compromised artifact ran in production for sixteen days before the supply chain research group's disclosure became public and a dependency scanner in the company's CI pipeline flagged the compromised package version in a newly built artifact. The security team initiated an investigation and discovered that the compromised version had been running in production since the staging promotion. The environment variables accessible to the sandbox containers included customer API tokens for the SaaS integrations the workflow platform supported — tokens that had been exfiltrated to the attacker's endpoint on the first execution of each sandbox that ran during the nineteen-day window. The company's security team spent four days reconstructing which customer tokens were likely exposed, notified affected customers, coordinated token rotation with each integration provider, and engaged external incident response support for the forensic analysis. The investigation was complicated by the fact that the sandbox logs did not capture outbound network connections at the DNS level — only failed connections — so the exfiltration traffic was not in the log data. The company knew the payload exfiltrated environment variables, knew the window during which the compromised image ran, and could enumerate the customers whose workflow executions ran during that window. But they could not confirm from their own logs whether a specific customer's token had been transmitted. Every customer whose workflow executed during the nineteen-day window received notification. The remediation and customer communication consumed six weeks of engineering, security, and customer success effort. The base image tag had never been discussed as a decision. It appeared in the Dockerfile as a three-word line — FROM node:20-alpine — that looked like a configuration detail rather than a security boundary.

Structural properties set by the build artifact management decision

Three structural properties are determined when an engineering team establishes — or fails to establish — a build artifact management decision record: how completely the production artifact can be reconstructed and verified after the fact, how far back the emergency rollback window extends without requiring a rebuild, and how detectable a supply chain substitution attack is before it reaches production. None of these are labeled as decisions during the first CI configuration — they emerge from the defaults of the build toolchain, the artifact registry settings, and the dependency specification format, and they accumulate their consequences as the team, the customer data surface, and the security threat model grow.

Property 1: The artifact versioning model and the build reproducibility surface. An artifact version is reproducible when the same source commit, built by the same toolchain with the same dependency versions, produces the same artifact on every run. The reproducibility surface is the set of build inputs that can vary between runs without changing the source commit — dynamically resolved dependency versions, mutable base image tags, external resources downloaded at build time, and embedded timestamps. Every input on the reproducibility surface is a potential source of divergence between the original artifact and a rebuild from the same commit. In a forensic reconstruction scenario — an incident retrospective, a security investigation, an audit of what was in production during a specific window — divergence between the original and the rebuild means the reconstruction is approximate rather than exact. The decisions never written down in the build artifact domain include not just which artifact format to use but how completely the build inputs are locked — whether the dependency specification pins exact versions or ranges, whether the base image is pinned by digest or by mutable tag, whether external resources downloaded at build time are fetched from a content-addressed store or from a URL that resolves to whatever is current. The new CTO onboarding problem in the artifact domain is the incoming technical leader who asks "can you show me exactly what was running in production during the incident window last quarter?" and receives the honest answer "we can show you the tag that was deployed, but we cannot rebuild from that tag and guarantee we'll get the same artifact, because the build process resolves some dependencies dynamically." The CI/CD pipeline decision record connects at the build process layer: the pipeline configuration determines which inputs are fixed at build time and which are resolved dynamically; the artifact versioning decision record establishes the requirement that the pipeline must satisfy — every build input must be pinned in a way that allows the build to be reproduced — and the pipeline configuration is the implementation of that requirement.

Property 2: The retention policy and the emergency rollback window. An artifact that no longer exists in the registry cannot be redeployed without a rebuild. A rebuild from the same commit may not produce the same artifact if the build environment has changed since the original build — which means a rebuilt "rollback" artifact may behave differently from the version that was running before the problematic deploy. The retention policy determines how many versions are available in the registry at any point in time, and therefore how far back the emergency rollback window extends without requiring a rebuild cycle. The cost pressure on artifact storage is real: container images are large, registry storage has a per-GB cost, and a naive policy of retaining every build indefinitely accumulates storage bills proportional to the deploy frequency. But the retention policy that balances storage cost against rollback window length is a tradeoff that should be made explicitly, with knowledge of the incident response plan's rollback requirements, rather than discovered when a rollback is needed and the required artifact is gone. The release process decision record connects at the rollback protocol layer: the emergency deploy protocol specifies the maximum rollback depth — how many versions back the team may need to go to find a stable artifact — and this maximum depth is the minimum retention requirement; the retention policy must be at least as long as the maximum rollback depth implied by the release process; when a cost optimization changes the retention policy, the release process decision record must be reviewed to confirm the rollback protocol assumptions remain valid. The audit log decision record connects at the compliance evidence layer: SOC 2 Type II and similar frameworks require evidence of what was deployed during the audit period; the artifact itself may need to be available for inspection, not just the deployment log that references it; the retention policy should distinguish between artifacts that were never deployed to a production environment (which can be pruned aggressively) and artifacts that were deployed to production (which should be retained for the audit evidence period regardless of age).

Property 3: The artifact signing policy and the supply chain integrity detection surface. An unsigned artifact is an artifact whose provenance is claimed by its registry location and tag, both of which are mutable. The registry owner can push a different image to an existing tag. The CI pipeline can be compromised to build from a different source. A dependency can be replaced with a compromised version between the time the lockfile was written and the time the build runs. Signing closes these gaps by attaching a cryptographic attestation to the artifact at the moment it is built: this artifact was produced by this pipeline, from this source commit, using this build toolchain. The attestation can be verified at deploy time — before the artifact reaches production — rather than detected after behavioral anomaly analysis weeks later. The CI/CD pipeline security decision record connects at the pipeline hardening layer: the signing policy specifies what must be attested at build time; the pipeline security policy specifies how the pipeline itself is protected from compromise — because a compromised pipeline can produce a signed attestation for a compromised artifact if the signing key or signing identity is not appropriately controlled; the two records together define the security boundary of the build process. The container image build policy decision record connects at the base image layer: digest pinning and signing policy together close the supply chain substitution surface at the base image boundary — digest pinning prevents a mutable tag from resolving to a different image, signing policy verifies that the image and its dependencies were assembled by an expected pipeline from expected sources. The secrets management decision record connects at the signing key lifecycle layer: the signing keys or OIDC-based signing identities used to attest build provenance are credentials with a lifecycle — they are issued, used, and must be rotable in the event of compromise; if the signing key is compromised, every artifact attested with it is no longer trustworthy, and the revocation and re-attestation process is simpler and faster when the signing key lifecycle is documented before it needs to be exercised. The WhyChose extractor finds the artifact management discussions in your AI session history — the build configuration session where the Dockerfile was written and a choice was made between tagging by version and tagging by commit SHA, the cost optimization sprint where artifact retention was reduced from thirty to ten versions and the rollback implications were not in the room, the incident retrospective where someone noted that rebuilding from the same commit produced a different binary and the follow-up item was added to a backlog and never revisited — and surfaces those discussions so you can evaluate which artifact management assumptions are still load-bearing as your customer data surface, compliance requirements, and security threat model grow.

The build artifact management ADR: five sections

Section 1: Artifact versioning and immutability model. Specify how artifacts are identified, versioned, and stored. The artifact identifier should be immutable: once an artifact is published to the registry with a given identifier, that identifier must always refer to the same artifact content. For container images, this means publishing with a content digest in addition to a semantic version tag — the digest is the immutable identifier, the tag is the human-readable alias. For compiled binaries, this means computing a content hash at publish time and storing the hash alongside the artifact. Specify the artifact identifier format: whether it includes the build date, the commit SHA, the semantic version, or some combination. Record the rationale: why this format was chosen, what operational use cases it supports (searching for all artifacts from a specific commit, identifying the artifact from a production pod log, correlating a customer-reported artifact version to the source commit that produced it). Include in this section the policy on mutable tags: are tags like latest or staging used as mutable pointers in the deployment pipeline? If so, document this explicitly as a known tradeoff — mutable tags allow simple pipeline configurations but mean that a tag reference in a deployment manifest does not uniquely identify the artifact that was actually deployed, which impairs forensic reconstruction. If mutable tags are used, specify that the deployment pipeline must also log the immutable digest of the pulled artifact alongside the tag reference so that the actual artifact can be recovered from the deployment log. Connect this section to the CI/CD pipeline decision record for the pipeline configuration requirements that enforce the immutability policy — specifically the steps that compute and publish the content digest and the registry configuration that prevents tag overwrites for version-tagged releases.

Section 2: Build reproducibility requirements and dependency pinning. Specify the reproducibility standard the team will maintain: which build inputs must be pinned, how pinning is enforced, and how reproducibility is verified. The minimum viable pinning requirement for most teams covers three layers: the source commit (always a specific Git SHA in CI, not a branch reference), the application dependency tree (a lockfile that pins every direct and transitive dependency to an exact version — package-lock.json, poetry.lock, Cargo.lock, or the equivalent for the stack), and the build toolchain (a specific version of the compiler, runtime, and base image, specified by immutable digest rather than mutable tag). For each layer, record both the pinning mechanism and the enforcement point: who or what prevents a build from running when the pinning requirement is not satisfied. Record the known sources of non-reproducibility that the team has accepted as tradeoffs: embedded timestamps in build artifacts, tool-version identifiers inserted by the build toolchain, or platform-specific builds for multi-architecture deployments. These accepted exceptions should be documented as known limitations, not discovered as surprises during a forensic reconstruction. Specify the reproducibility verification procedure: periodically rebuild a recently tagged release from its commit SHA and compare the result to the stored artifact, documenting any divergences and their causes. The verification cadence should be at least quarterly for production artifacts. Connect this section to the container image build policy decision record for the base image pinning requirement — specifically the policy on digest pinning versus tag pinning for base images, which is the most common source of non-reproducibility in container-based deployments and the most common entry point for supply chain substitution attacks.

Section 3: Artifact signing and supply chain integrity verification. Specify whether artifacts are signed, what is attested in the signature, and how signatures are verified at deploy time. For teams without dedicated security engineering, the minimum viable signing implementation uses keyless signing with OIDC-based identity — available via Sigstore Cosign in GitHub Actions, GitLab CI, and most managed CI environments — which does not require managing long-lived signing keys and produces a provenance attestation that records the source repository, the commit SHA, the pipeline run ID, and the build environment. The attestation is stored alongside the artifact in the registry. The deploy-time verification step checks that a valid attestation exists for the artifact being deployed and that the attestation claims the artifact was built from the expected source repository. This two-step model — attest at build, verify at deploy — is not a complete supply chain security implementation, but it closes the most common attack surface: a compromised dependency producing an artifact that reaches production without any verification that it was produced by an expected build process from an expected source. For teams with higher security requirements, specify the full attestation scope: what build inputs are captured in the provenance (dependency list, base image digest, build environment variables), whether SBOM (Software Bill of Materials) generation is required alongside the provenance attestation, and whether third-party audit tools can independently verify the attestation chain. Specify the escalation path when signature verification fails at deploy time: who is notified, what the default behavior is (block deploy or alert and allow), and how the investigation proceeds. Connect this section to the CI/CD pipeline security decision record for the pipeline hardening requirements that protect the integrity of the signing process itself — the signing identity must be bound to the specific pipeline that produces production artifacts, not available to all pipeline runs, so that a compromised fork or branch pipeline cannot produce valid attestations for production artifacts.

Section 4: Retention policy and emergency rollback window. Specify the artifact retention policy as three distinct tiers, each driven by a different requirement. The first tier is the emergency rollback window: the set of recent artifacts that must be available for immediate redeployment without a rebuild. This window should be defined in terms of time (for example, all artifacts from the last 90 days) rather than artifact count (the last N builds), because time-based retention aligns with the incident response timeline — a team recovering from an incident that was introduced in a feature shipped six weeks ago needs to access six-week-old artifacts, and a count-based retention may not cover that window if the deploy frequency is high. The second tier is the compliance evidence window: any artifact that was ever promoted to a production environment should be retained for the duration of the compliance audit period — 12 months for SOC 2 Type II — regardless of its age. This tier should be implemented as a promotion flag: when an artifact is deployed to production, it is marked as a production artifact and excluded from the age-based pruning that applies to non-promoted artifacts. The third tier is the build artifacts that were never promoted: these can be pruned aggressively based on storage budget. 14 to 30 days is a reasonable retention window for non-promoted artifacts in most environments. Record the storage cost at each tier and the business justification for the window length, so that future cost optimization discussions have an explicit constraint to evaluate rather than an implicit assumption to discover. Specify the procedure for reviewing the retention policy: when the release cadence, the rollback depth, or the compliance framework requirements change, the retention policy review must be triggered. Connect this section to the release process decision record for the rollback procedure's maximum depth requirement, which is the binding constraint on the emergency rollback window length — the retention policy cannot be shortened below the rollback depth without changing the incident response plan.

Section 5: Artifact promotion model and environment controls. Specify how artifacts move between environments — from build to development, from development to staging, from staging to production — and what verification steps are required at each promotion gate. The promotion model serves two purposes: it ensures that the artifact deployed to production has been tested in lower environments rather than built fresh at deploy time, and it creates an auditable record of which artifact version was in each environment at each point in time. For the artifact management decision record, the promotion model determines which artifacts receive the compliance evidence retention flag (any artifact promoted to production), which artifacts are candidates for supply chain verification at promotion time (any artifact crossing a trust boundary, such as moving from an external registry to an internal one), and how the artifact lineage is recorded — the chain from the source commit to the production artifact, through each environment it passed through, with timestamps and authorizations. Specify whether artifacts are rebuilt between environments or promoted as immutable artifacts: rebuilding between environments means that the artifact that passes staging testing is not the artifact that is deployed to production, which is a reproducibility gap; immutable promotion means that the artifact is built once and promoted through environments, so the artifact in production is demonstrably the same artifact that passed all tests in lower environments. This is the stronger model and should be the default unless there is a specific reason (such as environment-specific compilation or configuration embedding) that requires environment-specific builds. When environment-specific builds are required, document the specific difference and specify how the environment-specific modification is applied — as a configuration layer over an identical base artifact, not as a full rebuild from source. Connect this section to the CI/CD pipeline decision record for the pipeline architecture that implements the promotion gates, and to the audit log decision record for the deployment audit trail that records the artifact version, the environment, the promotion timestamp, and the authorizing identity for each promotion event.

FAQ

What makes a build reproducible, and how do you verify it?

A build is reproducible when running the same source code through the same build toolchain with the same dependency versions produces a byte-identical artifact on every run, regardless of when or where the build executes. The practical definition for most teams is somewhat weaker: the build is reproducible enough when the only differences between two builds from the same commit are timestamps embedded in the artifact metadata — differences that do not affect runtime behavior. Verifying reproducibility requires pinning three layers: the source commit (a specific Git SHA, not a branch name), the dependency tree (a lockfile that pins every transitive dependency to an exact version, not a range), and the build toolchain (a specific version of the compiler, runtime, and base image, specified by immutable digest rather than mutable tag). The simplest verification is to rebuild from a tagged commit after a week and diff the artifacts: if the diff contains anything beyond embedded timestamps, a build input changed. Common sources of non-reproducibility are package managers that allow patch-version ranges in lockfiles, base images pinned by mutable tag, build scripts that download external resources at build time without version-pinning, and code generation tools that embed the current timestamp in generated output. The goal is not perfect byte-level reproducibility — it is identifying and eliminating the sources of non-reproducibility that would prevent a forensic reconstruction of exactly what was in production during a given incident window.

How long should artifacts be retained in the registry before pruning?

The retention window should be set by two requirements that are often in tension: the emergency rollback window and the audit evidence retention period. The emergency rollback window is how far back you need to be able to deploy without a rebuild — driven by your incident response plan; if your rollback procedure calls for redeploying the previous version, you need at minimum the last N versions where N covers your maximum rollback depth; a practical minimum for teams with weekly deploy cadences is 90 days of artifacts. The compliance evidence period is how long SOC 2 Type II or similar frameworks require retaining evidence of what was deployed: typically 12 months. The retention policy should specify a minimum retention window for the most recent N versions (always retained regardless of age), a secondary retention window for artifacts that were ever promoted to production (retained for the audit period regardless of how many newer versions exist), and a pruning policy for artifacts that were built but never promoted (these can be pruned aggressively — 14 to 30 days is reasonable). Document the policy explicitly so that storage cost optimization discussions have a constraint to work against rather than discovering the constraint at the moment of a needed rollback.

What is the difference between pinning a container image by tag versus by digest?

A container image tag is a mutable pointer: the registry owner can push a new image to an existing tag at any time, changing what the tag resolves to without changing the tag name. A container image digest (sha256:...) is an immutable content hash: it identifies a specific, fixed set of image layers and cannot be reassigned. When a CI pipeline specifies FROM node:20-alpine, it is pulling whatever the registry owner has most recently pushed under that tag — which may differ between two pipeline runs separated by a week, or may have been replaced by a compromised version. When a CI pipeline specifies FROM node:20-alpine@sha256:abc123..., it is pulling the exact image layer set recorded when the digest was captured — any substitution would produce a different digest and the pull would fail or be flagged. The supply chain implication is direct: an attacker who can push to a public registry under a popular tag can substitute a compromised image; teams pinning by tag will pull it on their next build without any indication the image changed; teams pinning by digest will continue pulling the legitimate image they recorded. The operational burden of digest pinning is real — digests must be explicitly updated when a new base image version is desired — but this burden is the mechanism that makes supply chain substitution attacks visible before they reach production rather than after.

What should an artifact signing and attestation policy include for a team without a dedicated security engineering function?

For a team without dedicated security engineering, artifact signing should focus on two properties that provide most of the value with the lowest implementation overhead: build provenance attestation and deploy-time signature verification. Build provenance attestation means the CI pipeline, after producing an artifact, records a signed statement of what produced it — the source repository URL, the commit SHA, the pipeline run ID, and the build toolchain version — and publishes this attestation alongside the artifact. Sigstore's Cosign implements this with a single command in most CI environments using keyless OIDC-based signing that requires no long-lived key management. Deploy-time signature verification means the deployment pipeline, before deploying to production, verifies that a valid provenance attestation exists for the artifact and that the attestation matches expectations — specifically that the artifact was built from the expected repository rather than a fork or external source. The decision record should specify which artifact types are covered, which signing tool is used, where attestations are stored, what the verification policy is at deploy time, and what the escalation path is when verification fails.