GitHub Actions Cache Strategies That Actually Speed Things Up
Introduction¶
CI spends most of its time reinstalling the same dependencies. actions/cache
fixes that — but only if the key strategy is right. A misused cache silently
misses every run and you've gained nothing.
How the key works¶
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
key— exact match. If a cache with this key exists, restore it (a "cache hit") and skip saving at the end.restore-keys— prefix fallbacks, tried in order, newest match wins. On a hit here it's a partial restore; the job still saves a new cache underkeyat the end (because the exact key didn't match).- The
hashFiles(...)in the key means: when the lockfile changes, the key changes, you get a partial restore fromrestore-keys(last good deps), npm installs the delta, and a fresh full cache is saved.
Anti-pattern: a static key like key: npm-cache. It saves once and never
updates — you're permanently on the first run's dependencies.
Language dependencies — use the built-in caching¶
The setup-* actions have caching built in and handle the keys for you:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm # or yarn, pnpm
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip # cache-dependency-path for non-standard locations
- uses: actions/setup-go@v5
with:
go-version: "1.23"
cache: true # on by default
Only hand-roll actions/cache for things the setup actions don't cover (a
node_modules cache instead of ~/.npm, a build cache, a tool download).
Docker layer caching¶
Registry-backed is the most reliable in CI:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/org/app:latest
cache-from: type=registry,ref=ghcr.io/org/app:buildcache
cache-to: type=registry,ref=ghcr.io/org/app:buildcache,mode=max
type=gha (GitHub's cache backend) also works and needs no registry, but shares
the 10 GB repo limit and evicts aggressively. mode=max caches intermediate
stages too — essential for multi-stage builds.
The limits¶
- 10 GB per repository. When exceeded, GitHub evicts least-recently-used entries.
- Caches are scoped to a branch. A branch can restore from its own caches,
its base branch, and the default branch — not from sibling feature branches.
So the first CI run on a new branch restores from
main's cache. - Caches untouched for 7 days are deleted.
Verification and troubleshooting¶
- The job log shows
Cache restored from key: ...orCache not found for input keys: .... Check which. - Cache never hits on feature branches — expected on the first run; it
restores from the default branch. If
mainhas no cache either, seed it by running CI onmainonce. - Hit, but install still takes full time — you cached the wrong path
(
~/.npmdownload cache vsnode_modules), ornpm cideletesnode_modulesfirst (cache~/.npmfornpm ci, or cachenode_moduleskeyed on the lockfile and usenpm ci --prefer-offline). - Key changes every run —
hashFilesmatching a file CI generates (a timestamp, a computed lockfile). Hash only committed inputs. - Docker
cache-fromignored — nosetup-buildx-action(the default docker driver can't import caches), or thebuildcacheref doesn't exist yet (first run). - Cache size ballooning, evictions hurting — you're caching build outputs or
.git. Cache inputs, not artifacts; useactions/upload-artifactfor outputs. - Matrix jobs racing to save the same key — only the first save wins, the rest log a harmless conflict. Include the matrix dimension in the key if the contents differ per-variant.
Related tools and reading¶
- On-site: YAML validator, Crontab generator.
- Related posts: Secrets in CI without leaking them, Multi-stage Docker builds explained.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in DevOps & CI/CD
OpenTelemetry for Distributed Tracing
A trace follows one request across services. OpenTelemetry is the vendor-neutral way to produce them — here's the model, the Collector, and sampling that won't bankrupt you.
October 24, 2026pre-commit Hooks That Actually Help
pre-commit runs checks before a commit lands, catching formatting, secrets, and syntax errors locally. Here's a config that helps without being annoying.
October 24, 2026Chaos Engineering Basics
Chaos engineering is controlled experiments that inject failure to verify your system tolerates it. Here's the method, safe blast radius, starter experiments, and game days.
October 24, 2026