How to speed up CI builds with caching
Cache dependencies and build outputs in CI to cut pipeline time. Covers choosing cache keys, restore keys for partial hits, and avoiding stale state.
What and why
Most CI time goes to downloading dependencies and recompiling code. Caching stores those artifacts between runs and restores them when inputs are unchanged. Done correctly it cuts pipeline time dramatically; done wrong it serves stale data. This tutorial caches dependencies safely.
Prerequisites
- A working CI pipeline.
- A project with a lockfile (such as
package-lock.jsonorgo.sum). - Basic YAML knowledge.
Steps
1. Identify what to cache
Cache directories that are expensive to recreate and deterministic from inputs: the package cache, node_modules, or a compiler cache. Do not cache final deployable artifacts.
2. Build a stable cache key
The key must change only when inputs change. Hash the lockfile:
key: deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
A new lockfile produces a new key, forcing a fresh install.
3. Add the cache step
- uses: actions/cache@v4
with:
path: ~/.npm
key: deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
On a hit the path is restored before install; on a miss it is saved after the job.
4. Use restore keys for partial hits
restore-keys: |
deps-${{ runner.os }}-
If the exact key misses, a prefix match restores a recent cache, so install only fetches the delta.
5. Avoid caching stale state
Never include the cache in your build inputs, and exclude environment-specific files. Always run a clean, lockfile-driven install (such as npm ci) so a corrupt cache cannot poison results.
Verification
Run the pipeline twice. The first run shows a cache miss and saves the cache. The second shows a cache hit and a much faster install step. Compare total job times in the run summary.
Next Steps
Cache compiler outputs (such as the Go build cache) for further gains. Set a cache size budget and let old caches expire. Verify a clean run still works by temporarily disabling the cache.
Prerequisites
- A working CI pipeline
- A project with a dependency lockfile
- Basic YAML knowledge
Steps
- 1Identify what to cache
- 2Build a stable cache key
- 3Add the cache step
- 4Use restore keys for partial hits
- 5Avoid caching stale state
- 6Measure the improvement