CI Caching Explained: Speed Up Pipelines Without Breaking Them
Caching is the highest-leverage way to speed up CI - and a common source of subtle, hard-to-debug failures when keys are wrong.
Most CI time is spent re-doing work that has not changed: downloading the same dependencies, rebuilding the same layers. Caching stores those outputs and restores them on the next run. Done right it cuts minutes off every job; done wrong it serves stale data or silently never hits.
What is worth caching
- Dependency directories (node_modules, ~/.m2, ~/.cargo, pip wheels).
- Build outputs and compiler caches (incremental builds, ccache).
- Docker layers via a registry-backed layer cache.
Cache keys and restore keys
A cache key should change exactly when the cached content should change - typically a hash of your lockfile. Restore keys provide a fallback prefix so a near-miss still restores a useful older cache instead of nothing. Too-broad a key serves stale data; too-narrow a key never hits.
key: deps-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
deps-Common mistakes
- Keying on something that always changes (a commit SHA) → 0% hit rate.
- Keying on something too stable → stale dependencies served forever.
- Caching a directory that is rebuilt anyway, so the cache never helps.
- Ignoring cache size limits and silently evicting hot caches.
Key takeaways
- Cache the expensive, rarely-changing work: dependencies, build outputs, Docker layers.
- Key on a lockfile hash; use restore-keys as a fallback prefix.
- A wrong key either serves stale data or never hits - both look like "caching does nothing".