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
+------------------------------------------+| 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:
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:
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 fiPractical Commands
## GitHub Actions -- download artifact from a rungh run download RUN_ID --name coverage-report ## List all artifacts for a rungh api repos/OWNER/REPO/actions/runs/RUN_ID/artifacts ## GitLab -- download job artifactsglab ci artifact JOB_IDcurl --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 |
RememberThe 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 MistakeUsing 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.
TipUse
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.
SecurityArtifacts 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.