Speeding Up Workflows with Caching
Cache dependencies and build outputs across runs so CI installs in seconds instead of minutes.
Every workflow run starts on a fresh runner, so re-downloading dependencies each time is pure waste. Caching persists a folder between runs, keyed by a hash of your lockfile.
Two levels of caching
- Built-in setup caches β
actions/setup-node,setup-python, and friends take acache:input that handles the common case for you. - actions/cache β the general tool for anything: build artifacts, compiler caches, Docker layers.
Getting the key right
A good key includes a hash of the lockfile, so the cache invalidates precisely when dependencies change: ${{ hashFiles('**/package-lock.json') }}. Add restore-keys as a fallback prefix so a near-miss still restores most of the cache instead of starting cold.
Example
steps:
- uses: actions/checkout@v4
# Simple case: let setup-node manage the npm cache
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
# General case: cache a custom build directory
- uses: actions/cache@v4
with:
path: .next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: npm ci
- run: npm run buildWhen to use it
- A Node.js team caches node_modules using the package-lock.json hash so subsequent CI runs skip npm install and save 60 seconds per run.
- A Python project caches pip's download cache keyed to requirements.txt so dependency installation only re-runs when requirements change.
- A monorepo uses Nx affected-only runs combined with dependency caching to reduce a 15-minute CI pipeline to under three minutes on typical PRs.
More examples
Cache npm dependencies by lockfile hash
The cache: npm option in setup-node automatically keys the node_modules cache to package-lock.json, restoring it on cache hit.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm # built-in npm cache support
- run: npm ci
- run: npm testManual cache with custom key and restore
The restore-keys fallback restores the closest older cache when the exact key misses, so pip only downloads truly new packages.
steps:
- uses: actions/cache@v4
id: pip-cache
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
restore-keys: pip-${{ runner.os }}-
- run: pip install -r requirements.txt
- run: pytestCache Gradle build outputs
Caches both the Gradle dependency cache and the wrapper binary, keyed to all Gradle files, cutting Java build times on cache hits.
steps:
- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*','gradle-wrapper.properties') }}
- run: ./gradlew build
Discussion