Skip to main content

Artifact

A CI/CD artifact is a file or set of files produced by one pipeline job and passed to subsequent jobs or stored for later use. Common artifacts include Docker image references, compiled binaries, test reports, and coverage files that travel through the pipeline.

Understanding CI/CD Artifacts

What Is an Artifact in Simple Terms

An artifact is the pipeline's handoff package. When the Build job finishes compiling your application and building a Docker image, it does not hand the next job the source code — it hands it the built image reference. That image reference is the artifact.

The most important artifact discipline in CI/CD: build once, deploy everywhere. The Docker image built and tested in CI is the exact same image — same SHA256 digest — that gets deployed to staging and then to production. Any team that rebuilds the image for each environment is deploying untested code.

How It Works

Bash
+------------------------------------------+
| Job: build |
| Builds Docker image |
| Pushes to ECR with git SHA tag |
| Outputs: image tag = abc123 |
+------------------------------------------+
|
artifact: image tag
|
v
+------------------------------------------+
| Job: test |
| Pulls image abc123 from ECR |
| Runs tests against that exact image |
+------------------------------------------+
|
artifact: test-results.xml
|
v
+------------------------------------------+
| Job: deploy-staging |
| Deploys image abc123 to staging |
| Same image that passed tests |
+------------------------------------------+
|
v
+------------------------------------------+
| Job: deploy-production |
| Promotes SAME image abc123 to production |
| NOT rebuilt -- what was tested ships |
+------------------------------------------+

Artifact vs cache -- the important difference:

Bash
Artifact:
Purpose: pass build outputs between jobs
When: job A produces something job B needs
Example: compiled binary, Docker image tag,
test results XML, coverage report
Lifecycle: lives for the duration of the pipeline run
(or retention period)
Cache:
Purpose: speed up repeated operations
When: same downloads happen on every run
Example: node_modules, pip packages,
Maven .m2 directory
Lifecycle: persists between pipeline runs
(keyed by lockfile hash)

GitHub Actions artifact usage:

YAML
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Run tests with coverage
run: npm test -- --coverage
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always() ## upload even if tests failed
with:
name: coverage-report
path: coverage/
retention-days: 30
coverage-gate:
needs: test
runs-on: ubuntu-latest
steps:
- name: Download coverage report
uses: actions/download-artifact@v4
with:
name: coverage-report
- name: Check coverage threshold
run: |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage $COVERAGE% is below 80% threshold"
exit 1
fi

Practical Commands

Bash
## GitHub Actions -- download artifact from a run
gh run download RUN_ID --name coverage-report
## List all artifacts for a run
gh api repos/OWNER/REPO/actions/runs/RUN_ID/artifacts
## GitLab -- download job artifacts
glab ci artifact JOB_ID
curl --output artifact.zip \
"https://gitlab.com/api/v4/projects/PROJECT_ID/jobs/JOB_ID/artifacts"

Troubleshooting

Symptom Check What to Look For
Artifact not found downstream upload/download name Names must match exactly
Artifact too large path glob Only upload necessary files
Old artifacts consuming storage retention-days Set retention policy on all artifacts
Remember

The artifact promotion principle is non-negotiable in production pipelines. If your deploy-production job builds a fresh Docker image instead of promoting the image that staging tested, you are deploying code that was never tested in that exact form. Tag images with the Git commit SHA to ensure immutability and traceability.

Common Mistake

Using artifact storage for Docker images. Docker images belong in a container registry (ECR, GCR, Docker Hub, GitLab Registry). Store the image tag as an artifact — a small text value — and pull the image from the registry in downstream jobs.

Tip

Use if: always() when uploading test result artifacts. By default, an upload step is skipped if a previous step failed. But test results are most useful when tests fail — that is when you need the JUnit XML to understand what broke. if: always() ensures the upload runs regardless of test outcome.

Security

Artifacts can contain sensitive data — test database dumps, debug logs with credentials, coverage reports that reveal internal architecture. Set appropriate retention periods (retention-days: 7 for most artifacts) and consider whether artifacts from public repositories are publicly accessible in your CI platform.

Frequently Asked Questions

Why not just rebuild the code in every pipeline stage instead of passing artifacts between jobs?

Rebuilding at each stage risks non-determinism — a different dependency version resolving, a flaky compiler flag, or an environment difference between the build stage and deploy stage meaning what you tested isn't exactly what ships. Passing the same built artifact (a Docker image digest, a compiled binary) through every stage guarantees the thing verified by tests is bit-for-bit identical to the thing deployed.

What's a common mistake with how teams manage CI/CD artifacts?

Tagging Docker images with mutable tags like `latest` or a branch name instead of an immutable identifier like a Git SHA or build number — this makes it impossible to know exactly what code is running in production at any given moment, and can cause a later push to silently overwrite what a previous deploy referenced. Always reference artifacts by immutable digest or unique version, not a tag that can move.