On this page
  1. What is a build artifact?
  2. Why should you build once and promote?
  3. What belongs in an artifact?
  4. Why does my artifact keep breaking between environments?
  5. Where AI coding assistants get this wrong
  6. Checklist
  7. FAQ
    1. Is a Docker image a build artifact?
    2. What is the difference between an artifact and a cache?
    3. Should tests run before or after the artifact is built?
    4. How long should I keep build artifacts?
  8. Related topics
  9. Sources
concept

What Is a Build Artifact (and Why Does Yours Keep Breaking)?

A build artifact is the immutable output of a build, identified by a digest. Passing it between stages stops environments from diverging.

Quick answer

  • A build artifact is the file or image produced by a build step and consumed unchanged by later steps, identified by a content digest rather than a branch name.
  • Artifacts include container images, tarballs, JARs, static bundles, and compiled binaries; the digest is what makes them immutable in practice.
  • Deploying a branch name or latest tag means the deployed content can change without the pipeline changing, which breaks reproducibility.
  • An artifact should contain built code and production dependencies, and no secrets, development dependencies, source maps you would not publish, or environment-specific configuration.
  • Artifact retention is a cost decision: keep what you can redeploy and what you need for an audit, and let the rest expire.

What is a build artifact?

A build artifact is the output of a build that is stored and then consumed by later stages. It can be a container image, a tarball, a static bundle, a JAR, or a compiled binary. What makes it an artifact rather than a directory is that it is addressable and immutable: a registry digest or a checksum identifies exactly those bytes.

The branch that produced it is metadata, not identity. Two builds of the same branch produce different artifacts if a dependency changed, and the same artifact can be deployed to staging and production. Treating the digest as the identity is what lets you say with confidence that production runs what staging tested.

Why should you build once and promote?

Because a rebuild is a different artifact. Installing dependencies again can resolve a newer patch version, a base image tag can point at a new layer, and a build timestamp can change generated files. None of that shows up as a code change, so the difference between environments becomes invisible.

Promotion also makes rollback cheap. If production runs digest A and the new deploy fails, redeploying digest B is a single command with no build queue. When every environment rebuilds from source, rollback becomes a race against whatever the registry and package manager return today.

What belongs in an artifact?

The compiled or bundled application, the production dependency set pinned by the lockfile, and the runtime metadata needed to start it. Configuration such as database URLs and API keys belongs in the environment, injected at deploy time, never baked into the artifact, because an artifact is copied and cached in places you do not control.

Development dependencies, test files, and source maps are usually excluded to reduce size and exposure, though source maps are a deliberate trade-off if you need readable production stack traces. If the artifact is a container image, the same rule applies to layers: a secret added in one layer remains in the image history even if a later layer deletes the file.

# Build, tag by digest, and record what was tested.
docker build -t registry.example.com/app:${GITHUB_SHA} .
docker push registry.example.com/app:${GITHUB_SHA}

# Resolve the immutable reference once, then pass it to every stage.
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' \
  registry.example.com/app:${GITHUB_SHA})
echo "deploying ${DIGEST}"

# Deploy by digest, not by tag and never by branch name.
kubectl set image deployment/app app="${DIGEST}"

Why does my artifact keep breaking between environments?

Three causes account for most of it. The artifact was rebuilt per environment, so the bytes differ. The artifact depends on something outside itself — a mounted config file, a global package, a base image pulled at start — that differs between hosts. Or the artifact is mutable because it is referenced by a tag that gets overwritten.

The fixes follow directly: build once and promote by digest, make runtime dependencies explicit in the artifact or the deploy manifest, and forbid overwriting a published tag. Add an SBOM and a signature if you need to prove what is inside, and verify the digest at deploy time so a registry compromise cannot silently substitute an image.

Where this bites vibecoders

Generated CI files often run the build twice: once in the test job and again in the deploy job, with no artifact in between. It passes review because both jobs are green. The tell is a deploy step containing npm install or docker build. That deploy is not shipping what was tested; it is shipping whatever the registries return at that moment.

Where AI coding assistants get this wrong

  • Rebuilding the app inside the deploy job instead of downloading the tested artifact.
  • Deploying latest or a branch name instead of a digest.
  • Baking environment variables and API keys into the image at build time.
  • Overwriting an existing image tag so the same reference points at different bytes.
  • Copying .env files into the build context and leaving them in a layer.
  • Keeping artifacts forever with no retention policy and no way to tell which one is deployed.

Checklist

  • Produce one artifact per commit and record its digest.
  • Pass the digest, not the branch, between jobs.
  • Deploy by digest in every environment.
  • Keep secrets out of the build context and the image layers.
  • Set a retention policy for old artifacts.
  • Verify the digest before deploying and log it with the release.

FAQ

Is a Docker image a build artifact?

Yes. It is the most common one because it bundles the runtime as well as the code. The image digest is its identity, and deploying by digest rather than by tag is what makes the artifact immutable in practice, since tags can be moved.

What is the difference between an artifact and a cache?

An artifact is a deliverable that later stages consume and that you intend to deploy. A cache is an optimization that can be discarded at any time without changing the output. Mixing them is risky: if a deploy depends on a cache hit, a cache miss becomes a production failure instead of a slower build.

Should tests run before or after the artifact is built?

Both, for different tests. Unit and static checks can run before the build on source. Integration and end-to-end tests should run against the built artifact, because that is what will be deployed. The artifact is the unit of truth; testing source and deploying a build tests two different things.

How long should I keep build artifacts?

Long enough to redeploy any version you might need to roll back to, plus whatever your audit or compliance rules require. A practical default is the last twenty releases or ninety days, whichever is longer. Keeping everything is a storage bill; keeping nothing makes rollback impossible.

Sources

Share: