Caching Strategy Optimizer
Dramatically speed up CI pipelines with intelligent caching.
Cache Key Strategy
Package Manager Caches
NPM - Hash package-lock.json
- uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-npm-
pnpm - More aggressive caching
-
uses: pnpm/action-setup@v2 with: version: 8
-
uses: actions/cache@v3 with: path: | ~/.pnpm-store node_modules key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-
Python pip
- uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
Cargo/Rust
- uses: actions/cache@v3 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
Docker Layer Caching
Using Buildx
-
name: Set up Docker Buildx uses: docker/setup-buildx-action@v3
-
name: Build with cache uses: docker/build-push-action@v5 with: context: . cache-from: type=gha cache-to: type=gha,mode=max
Registry-based Cache
- name: Build with registry cache uses: docker/build-push-action@v5 with: context: . cache-from: type=registry,ref=myapp:buildcache cache-to: type=registry,ref=myapp:buildcache,mode=max
Build Output Caching
Next.js cache
- uses: actions/cache@v3 with: path: | ${{ github.workspace }}/.next/cache key: ${{ runner.os }}-nextjs-${{ hashFiles('/package-lock.json') }}-${{ hashFiles('/.js', '**/.jsx', '/*.ts', '/*.tsx') }} restore-keys: | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- ${{ runner.os }}-nextjs-
Webpack cache
- uses: actions/cache@v3 with: path: node_modules/.cache/webpack key: ${{ runner.os }}-webpack-${{ hashFiles('webpack.config.js') }}-${{ hashFiles('src/**') }}
TypeScript build cache
- uses: actions/cache@v3 with: path: | dist tsconfig.tsbuildinfo key: ${{ runner.os }}-tsc-${{ hashFiles('**/*.ts') }}
Test Results Caching
Jest cache
- uses: actions/cache@v3 with: path: /tmp/jest_rt key: ${{ runner.os }}-jest-${{ hashFiles('**/*.test.ts') }}
Pytest cache
- uses: actions/cache@v3 with: path: .pytest_cache key: ${{ runner.os }}-pytest-${{ hashFiles('**/test.py') }}
Before/After Metrics
Before Optimization
- Total time: 12 minutes
- npm ci: 4 minutes
- Build: 5 minutes
- Tests: 3 minutes
After Caching
- Total time: 3 minutes
- npm ci: 30 seconds (cache hit)
- Build: 1 minute (incremental)
- Tests: 1.5 minutes (cache hit)
Improvement: 75% faster (12m → 3m)
Cache Hit Rate Monitoring
-
name: Check cache hit id: cache uses: actions/cache@v3 with: path: node_modules key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }}
-
name: Log cache status run: | if [ "${{ steps.cache.outputs.cache-hit }}" == "true" ]; then echo "✅ Cache hit - saved $(date -u -d @$SECONDS +%M:%S)" else echo "❌ Cache miss - installing from scratch" fi
Best Practices
-
Precise keys: Include all dependencies in hash
-
Restore keys: Fallback to partial matches
-
Multiple paths: Cache related files together
-
Size limits: GitHub Actions limit is 10GB
-
Expiration: Caches expire after 7 days
-
Mode=max: Docker cache mode for better hits
-
Monitor hits: Track cache effectiveness
Common Pitfalls
❌ Too generic keys: key: deps (always hits) ✅ Specific keys: key: deps-${{ hashFiles('package-lock.json') }}
❌ Missing restore-keys: Cache miss on minor changes ✅ Restore keys: Partial match fallback
❌ Caching node_modules with wrong lock file ✅ Match lock file: Hash the right lockfile
Output Checklist
-
Package manager cache configured
-
Build output cache
-
Docker layer cache (if applicable)
-
Test cache configured
-
Cache keys use file hashes
-
Restore keys for fallback
-
Before/after metrics documented
-
Cache hit monitoring