27 / 48 · 16 CI/CD Pipelines · Pipeline Optimization← prev⊞ allnext →☰ Read as one page
5.2Caching
Caching is the single most impactful optimization. Without caching, every pipeline run downloads and installs dependencies from scratch -- often taking 2-5 minutes that add zero value.
What to Cache
| What | Cache Key | Typical Savings |
|---|---|---|
| Node modules | package-lock.json hash |
1-3 minutes |
| Python virtualenvs | requirements.txt or poetry.lock hash |
1-2 minutes |
| Playwright browsers | package-lock.json hash |
2-4 minutes |
| Docker layers | Dockerfile hash | 2-10 minutes |
| Gradle/Maven dependencies | build.gradle or pom.xml hash |
1-5 minutes |
| Go modules | go.sum hash |
30s-2 minutes |
GitHub Actions Caching
# Cache node_modules (automatic with setup-node)
- uses: actions/setup-node@v4
with:
node-version: 24
cache: 'npm'
# Cache Playwright browsers (manual)
- uses: actions/cache@v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- run: npx playwright install --with-deps
if: steps.playwright-cache.outputs.cache-hit != 'true'
The cache-hit output lets you skip installation entirely when the cache is valid. This turns a 3-minute Playwright browser installation into a 5-second cache restore.
Cache Invalidation Strategy
Cache keys should change when dependencies change and remain stable otherwise:
# Good: Changes only when lockfile changes
key: deps-${{ hashFiles('package-lock.json') }}
# Bad: Changes on every commit (cache is never used)
key: deps-${{ github.sha }}
# Better: Fallback to partial cache match
key: deps-${{ hashFiles('package-lock.json') }}
restore-keys: |
deps-
The restore-keys fallback finds the most recent cache that starts with deps-, which may be slightly stale but is much faster than installing from scratch.