CI cost figures are vendor list prices verified April 2026. Actual cost depends on plan, concurrency, and discount terms. Some links are affiliate links. See disclosure.

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.

CROSS-PLATFORM CACHE KEY PATTERN
# 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 ci
GOTCHA

Hash 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-lockfile
GOTCHA

Cache 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-lockfile
GOTCHA

Use 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.txt
GOTCHA

Hash 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-interaction
GOTCHA

Hash 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-progress
GOTCHA

Cache 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 build
GOTCHA

Gradle 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@v3
GOTCHA

ramsey/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
GOTCHA

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 ./...
GOTCHA

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 rspec
GOTCHA

bundler-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.”

SIGNAL

Cache hit rate below 40%

Action: Review cache keys. Too-specific keys prevent restore hits.

SIGNAL

Cache restore time above 60s

Action: Cache is too large. Split into smaller, more targeted caches.

SIGNAL

Builds failing with 'Cache not found'

Action: Cache was evicted. Consider separating small caches (lockfile hash) from large caches (build artifacts).

SIGNAL

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