How to cache dependencies in GitHub Actions (and cut build time)
The first thing I check on a slow CI run is the cache, and the cache is usually missing. It is the cheapest speedup there is: one line on your setup step, and every run stops re-downloading and rebuilding the exact dependencies it already had yesterday. On a typical Node or Python project that is 30 to 90 seconds back, every run, forever. People skip it anyway, then pay GitHub to reinstall lodash for the ten-thousandth time.
The free win: built-in caching
You probably do not need actions/cache directly. The setup-* actions cache your package manager for you, you just have to turn it on:
# Node
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm' # or 'yarn' / 'pnpm'
# Python
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'Go (actions/setup-go) caches by default. Java (actions/setup-java) needs cache: 'gradle' (or 'maven') set explicitly. Check your setup step first, the one-line fix covers most projects.
Custom caching with actions/cache
For anything the setup actions do not cover (a build output, a tool the package manager does not own), reach for actions/cache with an explicit path and key:
- uses: actions/cache@v4
with:
path: ~/.cache/my-tool
key: ${{ runner.os }}-mytool-${{ hashFiles('**/lockfile') }}
restore-keys: |
${{ runner.os }}-mytool-Get the key right, or the cache never helps
The key should change only when your dependencies change, which is why it hashes the lockfile (hashFiles('**/package-lock.json')). The restore-keys prefix lets a run fall back to the most recent matching cache when the exact key misses, so a single dependency bump restores most of the cache instead of rebuilding everything.
Why your cache "isn't working"
- Caching the wrong directory: cache the package manager's store, not
node_moduleson most setups. - A key with no lockfile hash, so it never invalidates and you keep serving a stale cache.
- Cache scope: a branch reads its own cache plus the default branch's, but not a sibling branch's, so cross-branch hits can surprise you.
Find out if you're missing it
astro, for instance, has a release workflow with no dependency cache. Curious about yours? See real scorecards on the showcase, then scan your own.
These hide across however many workflow files you have, which is exactly why nobody sits down and fixes them. Point GitSpider at your repo and it flags which patterns apply, with the fix for each.
Scan your repo free