Last verified April 2026 · 8 min read
The dependency caching recipes every CI pipeline needs
A fresh npm install takes 60-180 seconds. Cached, it takes 3-10 seconds. On a 20-PR/day team, uncached dependency installs waste 60-90 minutes of billed CI minutes per day. Three quarters of pipelines are still doing this.
Below are copy-paste YAML recipes for 11 ecosystems, each with a gotcha note and a typical saving estimate. Implement all that apply to your stack.
# Always include: OS + architecture + lockfile hash
key: ${{ runner.os }}-${{ runner.arch }}-ecosystem-${{ hashFiles('**/lockfile') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-ecosystem-Including runner.arch prevents cache poisoning between x86 and ARM runners on mixed-architecture pipelines.
Node.js — npm
via actions/setup-node@v4
60-180s → 3-10s
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ciHash package-lock.json, not package.json. The lockfile guarantees reproducible installs.
Node.js — pnpm
via pnpm/action-setup@v4
90-120s → 5-15s
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
- run: pnpm install --frozen-lockfileCache the pnpm store directory (~/.local/share/pnpm/store). Use pnpm-lock.yaml as the cache key.
Node.js — yarn
via actions/setup-node@v4
90-150s → 5-15s
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- run: yarn install --frozen-lockfileUse yarn.lock as the cache key hash. Yarn Berry (v2+) uses a different store path.
Python — pip
via actions/setup-python@v5
60-180s → 10-20s
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install -r requirements.txtHash requirements.txt (or requirements-lock.txt if you pin transitive deps).
Python — Poetry
via actions/cache + poetry
90-200s → 10-25s
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install poetry
- uses: actions/cache@v4
with:
path: ~/.cache/pypoetry
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
- run: poetry install --no-interactionHash poetry.lock, not pyproject.toml. The lock file is the source of truth.
Java — Maven
via actions/setup-java@v4
120-300s → 20-40s
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
- run: mvn -B package --no-transfer-progressCache the local Maven repository (~/.m2). Include the JDK version in the cache key for multi-JDK pipelines.
Java — Gradle
via gradle/actions/setup-gradle@v4
180-600s → 30-60s
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- uses: gradle/actions/setup-gradle@v4
- run: ./gradlew buildGradle has two separate caches: the dependency cache (~/.gradle/caches) and the configuration cache. Cache both. The Gradle action handles both automatically.
PHP — Composer
via ramsey/composer-install@v3
60-120s → 5-15s
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- uses: ramsey/composer-install@v3ramsey/composer-install automatically handles the cache key and restore. It is the simplest single-action solution.
Rust — cargo
via Swatinem/rust-cache@v2
300-1800s → 30-120s
- uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - run: cargo build --release - run: cargo test
rust-cache caches the cargo registry and build artifacts. Rust builds are unusually slow (5-30 min cold) and benefit dramatically from caching.
Go — modules
via actions/setup-go@v5
30-120s → 5-20s
- uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
- run: go test ./...Go module cache is automatic with cache: true. The build cache (GOCACHE) is also cached. Hash go.sum for the cache key.
Ruby — bundler
via ruby/setup-ruby@v1
60-180s → 10-20s
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- run: bundle exec rspecbundler-cache: true handles all cache configuration automatically. Hash Gemfile.lock for the key.
Cache-size monitoring
GitHub Actions enforces a 10 GB repository-wide cache limit. When exceeded, the least recently used caches are evicted. Monitor your cache usage in the Actions tab under “Caches.”
Cache hit rate below 40%
Action: Review cache keys. Too-specific keys prevent restore hits.
Cache restore time above 60s
Action: Cache is too large. Split into smaller, more targeted caches.
Builds failing with 'Cache not found'
Action: Cache was evicted. Consider separating small caches (lockfile hash) from large caches (build artifacts).
Storage warning in GitHub billing
Action: Reduce cache retention or move large artifact caches to S3/R2.
DIGITAL SIGNET · PIPELINE AUDIT
We find every missing cache in your pipeline.
Digital Signet audits your workflow YAML, identifies uncached installs, and delivers a complete set of dependency caching diffs.
Get an Audit