CI/CD caching strategies in 2026: seven techniques and the savings each delivers
Caching is the single largest cost-reduction lever in CI. Most teams have one form of caching set up (dependency cache for npm or pip) and stop there, leaving 30-50% of potential savings on the table. This page walks through seven distinct caching techniques in 2026, the realistic saving each delivers, and the ordering that gets you the most bill reduction for the least platform-engineering time. The companion page on build cache savings covers the per-vendor cache mechanics; this page covers the strategies themselves.
Headline at a glance (2026)
Order of investment: dependency cache first (1-2 days, 15-30% saving), Docker layer cache via BuildKit second (3-5 days, 30-50% saving on container builds), test result cache third (1 week, 20-40% saving on stable test suites), distributed build cache fourth (2-4 weeks, 50-90% saving on monorepos). Each delivers more saving than the previous; each takes more engineering time.
1. Dependency cache (npm / pip / Maven / Cargo)
Install dependencies once; restore from cache on subsequent runs. Standard pattern across all CI vendors: hash the lockfile, use the hash as the cache key, restore the cache before the install step, fall back to a cold install if the cache misses, save the cache on success. Implementation effort: 1-2 days across all repos. Saving: 60-90% off cold-install time per build, 15-30% off the total monthly bill.
The actions are well-documented for every major vendor. actions/cache for GitHub Actions, the cache: stanza for GitLab CI, save_cache / restore_cache for CircleCI. Most language ecosystems have purpose-built actions: actions/setup-node@v4 with cache enabled, actions/setup-python@v5, etc. Use the purpose-built action where available; it handles cache key correctness for you.
2. Docker layer cache (BuildKit registry export)
For any pipeline that builds Docker images, layer caching delivers the second-largest saving. The naive approach (cache the full Docker daemon state via actions/cache) hits cache size limits quickly. The robust approach is BuildKit cache export to a container registry: each layer is stored separately and only changed layers are rebuilt on subsequent runs.
Implementation: configure docker buildx with --cache-to=type=registry,ref=ghcr.io/org/repo:cache and --cache-from=type=registry,ref=ghcr.io/org/repo:cache. First build populates the cache; subsequent builds restore the layers they need. Effort: 3-5 days including registry setup and first-build validation. Saving: 30-70% reduction in Docker build time, with biggest wins on multi-stage builds where the dependency layers rarely change.
3. Test result cache
For stable test suites, you can cache test results: if the source files a test depends on have not changed, the previous result is still valid. Tools like Jest cache, pytest-cache, and Gradle build cache implement this at the test-framework level. The CI side is to persist the framework cache directory between runs.
Saving: 20-40% on stable test suites where most tests touch unchanged code. The catch: cache invalidation is harder for tests than for dependencies, because a test result depends on transitive dependencies that the framework may not track perfectly. Conservative pattern: cache test results within a single PR (consecutive pushes share cache), invalidate on PR merge to main. The savings are smaller than full cross-PR caching but the correctness risk is much lower.
4. Build artifact cache
Distinct from artifact storage. Build artifacts that take long to produce (compiled binaries, generated code, asset bundles) can be cached and restored if the source files are unchanged. The build tool typically does this internally (Bazel, Gradle, sbt, ccache for C/C++), but ensuring the build-tool cache directory persists between CI runs is a CI-level configuration.
Saving: 30-60% on compile-heavy builds where most files are unchanged. C++ teams using ccache routinely report 70-90% recompile time reduction; Rust teams using sccache see similar. The Rust ecosystem in particular benefits enormously because cold cargo builds are notoriously slow. The trade-off: cache size grows quickly for compile artifacts, so tight eviction policies and bounded cache sizes are essential.
5. Distributed build cache (Bazel remote / Nx Cloud / Turborepo)
The next level. Distributed caches store individual compilation units (functions, modules, sub-projects) keyed by content hashes of their inputs. Any matching cache hit is reusable regardless of which developer or CI run produced it. The first developer to compile a particular function on a given input pays for the compile; everyone else hits cache instantly.
Tools: Bazel remote cache (self-hosted or via vendors like BuildBuddy), Nx Cloud, Turborepo Remote Cache, Gradle Build Cache. Implementation effort: 2-4 weeks for first deployment, ongoing operational maintenance. Saving: 50-90% on monorepos with many shared dependencies. Worth it for codebases above 100k lines or monorepos with 10+ services.
Cost trade-off: distributed caches require backend storage. Self-hosted Bazel cache on S3 + small EC2 service is roughly $50-200/month operational cost. Managed services (Nx Cloud, BuildBuddy hosted, Turborepo Remote) are typically $25-100/seat/month. For 50+ developer teams the saving on CI minutes vastly exceeds the cache infrastructure cost.
6. Container base image local mirror
Every CI run that uses a Docker base image pulls it from Docker Hub or another registry. For a team running 10,000+ builds/month, this is 10,000+ pulls of the same base image, each consuming bandwidth and adding seconds to the build. Pull-through cache or mirror solves it.
ECR Pull Through Cache (AWS) automatically caches upstream registry images in your account on first pull. Subsequent pulls in the same region are served from the local cache for free egress. Harbor and Artifactory provide similar functionality on-prem. Saving: 10-25% reduction in Docker build time, and a meaningful reduction in cross-region egress cost when CI runners are in different regions from the upstream registry.
7. Module / package mirror (Artifactory / Nexus / Verdaccio)
The same logic applied to language package registries. Every npm install pulls from npmjs.com; every pip install from pypi.org. A locally-hosted mirror caches each unique package version on first download and serves subsequent downloads locally. Tools: Artifactory, Nexus, Verdaccio (Node-only, lightweight).
Saving: 5-15% on dependency install time, plus reliability benefits (your CI is no longer dependent on the upstream registry being available). The cost case is borderline at small teams (the mirror is operational overhead) but improves at 100+ devs where availability and reliability of upstream registries is a real risk.
Putting it together: a sample roadmap
For a 25-50 dev scaleup with no caching today, the right ordering: month 1, dependency caching across all repos (15-30% bill reduction). Month 2, Docker layer caching via BuildKit on the container-heavy services (additional 20-30%). Month 3, test result caching where the test suite is stable (additional 10-20%). Months 4-6, distributed build cache if the codebase is monorepo-shaped (additional 30-50%). Container base image mirror and package mirror are additive but lower priority; defer until other moves are complete.
Compounded, the seven techniques can take a $3,000/month CI bill to $800-1,200/month. The work is real (multiple weeks of platform engineering across the year) but the ROI is the highest of any CI infrastructure investment available.
Frequently Asked Questions
What is the best caching strategy for CI/CD?
Start with dependency caching (npm, pip, Maven, Cargo). It delivers 15-30% bill reduction with two days of work and no operational overhead. Add Docker layer caching via BuildKit registry export second; another 30-50% reduction on container-heavy workloads. Add distributed caching (Bazel remote, Nx Cloud) third for monorepos with deep test suites. The ordering matters: do the easy wins before the harder structural ones.
What does dependency caching save?
Per-build, dependency caching cuts cold-install time 60-90%: a 90-second npm ci becomes a 5-second cache restore. Per-month, total bill saving is 15-30% because dependency install is rarely the entire build. The variance: Node and Go cache install is fast and the savings are smaller (10-20%); Java Maven/Gradle and Rust have severe cold-install penalties so caching saves 30-50%.
What is BuildKit cache export?
BuildKit cache export pushes Docker layer cache to a container registry (ECR, GHCR, Docker Hub) where it can be pulled by subsequent builds. The pattern: --cache-to=type=registry,ref=ghcr.io/org/repo:cache and --cache-from=type=registry,ref=ghcr.io/org/repo:cache. First build populates the cache, all subsequent builds restore individual layers as needed. Typical saving: 50-70% reduction in Docker build time after the cache is warm.
What is a distributed build cache?
A distributed build cache stores compilation results at the level of individual functions or modules across all CI runs and developers. Bazel remote cache, Nx Cloud, Turborepo Remote Cache, Gradle Build Cache. The cache key is a hash of the function inputs (source code, dependencies, build flags), and any matching cached output can be reused regardless of which developer or CI run produced it. Distributed caches deliver 50-90% reduction on monorepo CI but require non-trivial setup.
When does caching break the build?
Three failure modes. Stale cache hit: cache key includes the lockfile hash so different lockfiles never share a cache entry; missing this is the most common cache bug. Cache poisoning: a build writes corrupt data to the cache; mitigation is to only push to cache from trusted main-branch builds, not from PRs. Runaway cache size: explicit eviction logic for branches that no longer exist. Each failure mode is preventable with the right cache key strategy and access control.