GitHub Actions Tutorial — Dependency Caching
Dependency Caching
Downloading dependencies from the internet on every workflow run wastes time and bandwidth. Caching saves these files across runs, turning a 2-minute npm install into a 5-second cache restore.
Artifacts vs Cache
| Feature | Artifacts | Cache |
|---|---|---|
| Purpose | Persist workflow outputs | Speed up dependency installs |
| Lifetime | 90 days (configurable) | 7 days since last access |
| Scope | Available after workflow | Available across workflow runs |
| Sharing | Between jobs in a run | Between runs on same branch |
| Action | upload-artifact/download-artifact |
actions/cache |
Setup Action Caching (Easiest)
The setup-* actions have built-in caching:
# Node.js
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm' # caches ~/.npm
# Python
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip' # caches pip downloads
# Java (Maven)
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven' # caches ~/.m2/repository
# Go
- uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true # caches ~/go/pkg/mod
This is the simplest approach — one line adds caching.
Manual Cache with actions/cache
For more control, use actions/cache directly:
steps:
- uses: actions/checkout@v4
- name: Cache node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
node-${{ runner.os }}-
- run: npm ci
How It Works
- Restore: Before
npm ci, check if a cache with the exactkeyexists - Hit: If found, restore
node_modules—npm ciruns instantly - Miss: If not found, try
restore-keysprefix matches - Save: After the job, save
node_modulesunder thekey
Cache Key Anatomy
node-Linux-abc123def456
│ │ └── hashFiles('package-lock.json')
│ └── runner.os
└── prefix
The key changes when package-lock.json changes (new dependencies), forcing a fresh install and new cache.
Language-Specific Cache Paths
| Language | Package Manager | Cache Path |
|---|---|---|
| Node.js | npm | ~/.npm or node_modules |
| Node.js | yarn | ~/.cache/yarn |
| Node.js | pnpm | ~/.local/share/pnpm/store |
| Python | pip | ~/.cache/pip |
| Java | Maven | ~/.m2/repository |
| Java | Gradle | ~/.gradle/caches |
| Go | modules | ~/go/pkg/mod |
| Rust | Cargo | ~/.cargo/registry + target |
Common Mistakes
| Mistake | Fix |
|---|---|
| Cache never hits | Check that the key includes the lock file hash |
| Stale cache | Change the key prefix to bust the cache |
| Caching the wrong path | Cache the download cache (~/.npm), not node_modules, for setup-* actions |
| Cache too large | GitHub limits cache to 10 GB per repo; prune unused caches |
Quick Reference
| Concept | Syntax |
|---|---|
| Built-in cache | setup-node with cache: 'npm' |
| Manual cache | actions/cache@v4 with path, key, restore-keys |
| Cache key | key: prefix-${{ runner.os }}-${{ hashFiles('lock-file') }} |
| Restore keys | Prefix-based fallback matching |
| Max size | 10 GB per repository |
| Expiry | 7 days since last access |
← Previous: Download Artifacts
Next: Cache Strategies →