Caching and Build Artifacts
As pipelines grow, redundant work becomes the biggest tax on developer velocity. Every pipeline run that reinstalls the same npm packages, redownloads the same Maven dependencies, or recompiles unchanged code from scratch wastes minutes that add up across hundreds of daily runs. Caching and artifacts are the two mechanisms CI/CD systems provide to avoid this waste, but they solve different problems and are frequently confused. Caching persists reusable inputs — dependency trees, compiled toolchains, package manager stores — across separate pipeline runs so the next run can skip expensive downloads or installs. Artifacts, by contrast, carry outputs of one job forward to another job within the same pipeline run, such as passing a compiled binary from a build job to a deploy job, or a test report from a test job to a publish job. Understanding this distinction is essential to designing pipelines that are both fast and correct.
Cricket analogy: Virat Kohli doesn't re-learn his stance before every net session (cached muscle memory), but the specific scorecard from today's match against Australia is a fresh output carried to the next day's team review — caching skips setup, artifacts carry results forward.
How Dependency Caching Works
Most CI platforms implement caching as a key-value blob store scoped to the repository or branch. You supply a cache key, typically derived from a hash of a lockfile (package-lock.json, poetry.lock, Gemfile.lock), and a set of paths to persist (node_modules, ~/.m2, ~/.cache/pip). On a cache hit, the platform downloads and restores those paths before your build steps run, skipping the installation step entirely or making it a no-op. On a cache miss — for example when the lockfile changes — the pipeline falls back to a fresh install and then saves a new cache entry under the new key. Well-designed cache keys include a restore-fallback chain: an exact match on the lockfile hash first, then a broader prefix match, so a partial cache is still better than none when dependencies shift slightly.
Cricket analogy: A groundskeeper preps the pitch to a spec matching the toss report (cache key = pitch report); if conditions match exactly, the same roller and grass length are reused (hit), but if weather shifts they fall back to the nearest similar preparation rather than starting from bare earth.
How Build Artifacts Work
Artifacts are explicit outputs uploaded by one job and downloaded by another, and unlike caches they are treated as the authoritative result of that run rather than a performance optimization. A build job compiles source into a binary or bundle, uploads it as a named artifact, and a downstream deploy job downloads that exact artifact rather than rebuilding it. This guarantees that what gets tested is exactly what gets deployed — a principle often called 'build once, deploy everywhere.' Artifacts are also commonly used to retain test reports, coverage output, and logs for post-run inspection, and most platforms let you configure a retention period after which they are automatically deleted to control storage costs.
Cricket analogy: The exact bat Rohit Sharma gets inspected and certified by umpires before the match is the one he must use throughout — you don't swap in an uncertified replacement mid-innings; certified equipment carries forward untouched, and old certification stickers are eventually retired.
name: build-and-cache
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache node_modules
id: npm-cache
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: dist-bundle
path: dist/
retention-days: 5
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: dist-bundle
path: dist/
- name: Deploy
run: ./scripts/deploy.sh dist/Think of caching like keeping ingredients pre-chopped in your fridge for tomorrow's meal — it speeds up a similar but separate cooking session. An artifact is more like handing a finished dish from the kitchen straight to the person plating it in the very same meal service — it must be the exact thing, not a fresh approximation.
A common pitfall is caching build output itself (e.g. the dist/ folder) instead of dependencies, and then trusting a stale cache hit to skip the actual build step. Caches are best-effort and can be evicted or restored inconsistently across runners, so never treat a cache as a substitute for reproducible builds — only cache inputs that are safe to regenerate deterministically.
Cache Invalidation and Storage Limits
Cache invalidation is deliberately manual in most systems: you control it entirely through the cache key. If the key never changes, a corrupted or outdated cache can silently persist for months. Most platforms also enforce per-repository storage quotas and evict the least-recently-used caches once the limit is reached, so teams with many branches should scope cache keys carefully (often including a branch or job-matrix identifier) to avoid thrashing between unrelated cache entries competing for the same slot.
Cricket analogy: If a groundskeeper never updates a watering label after switching soil mix, the pitch report silently misleads the captain for months; grounds also have limited storage for prepared pitches, so the oldest unused one gets dug up first when a new one is needed.
- Caching persists reusable inputs (dependencies, toolchains) across separate pipeline runs to save time.
- Artifacts carry outputs (binaries, reports) between jobs within a single pipeline run.
- Cache keys should be derived from lockfile hashes with fallback restore-keys for partial hits.
- Artifacts enforce 'build once, deploy everywhere' by ensuring tested and deployed outputs are identical.
- Caches are best-effort and evictable — never rely on them for correctness, only for speed.
- Both caching and artifacts have retention/storage limits that require lifecycle management.
Practice what you learned
1. What is the primary difference between a cache and an artifact in CI/CD?
2. Why should cache keys typically be derived from a lockfile hash?
3. What does the 'build once, deploy everywhere' principle rely on?
4. Why is it risky to rely on a cache for correctness rather than just speed?
5. What commonly causes cache thrashing across branches in a shared repository?
Was this page helpful?
You May Also Like
Automating Builds in Pipelines
How CI systems turn source code into reproducible build artifacts automatically, covering build triggers, dependency resolution, and artifact publishing.
Designing Multi-Stage Pipelines
Learn how to structure a CI/CD pipeline into logical stages — build, test, package, deploy — so failures surface early and each stage has a clear contract with the next.
Parallelizing Pipeline Jobs
Explore techniques for running pipeline jobs concurrently — matrix builds, test splitting, and fan-out/fan-in patterns — to cut wall-clock time without sacrificing reliability.
Building Docker Images in Pipelines
Understand how CI/CD pipelines build, tag, and optimize Docker images, including layer caching, multi-stage builds, and build-time security checks.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics