GitHub Actions Tutorial — Dependency Caching

2 minute read

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

  1. Restore: Before npm ci, check if a cache with the exact key exists
  2. Hit: If found, restore node_modules — npm ci runs instantly
  3. Miss: If not found, try restore-keys prefix matches
  4. Save: After the job, save node_modules under the key

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 →

Updated: