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 |
REMEMBER THIS**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 / WARNING**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.
PLACEMENT PRO TIP**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.
COMMON MISTAKE / WARNING**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.