Speeding Up CI with GitHub Actions Cache: A Practical Guide
Learn how to use GitHub Actions caching to cut CI build times, set up cache keys, avoid stale data, and understand limits. Step‑by‑step example for Node.js and practical trade‑offs.
07 Sept 2025, 17:30 UTC

Problem: CI Slowness Drains Productivity
In most open‑source and enterprise projects, every push triggers a full build and test cycle. When dependency resolution or compilation takes minutes, developers wait for feedback, slowing iteration. The root cause is often the repeated download and build of the same artifacts in each workflow run.
Thesis: Reuse What You Can with GitHub Actions Cache
GitHub Actions provides a built‑in cache that stores files between workflow runs. By persisting dependency directories (e.g., node_modules, ~/.m2, vendor) or compiled artifacts, you can reduce a multi‑minute build to a few seconds. The cache is keyed, so you control when a new copy is fetched.
1. How the Cache Works
The cache is a simple key‑value store attached to your repository. When a workflow starts, the actions/cache step looks for a key that matches the current run. If it finds one, the cached files are extracted before the rest of the job runs. If not, the step runs the job, then uploads the specified files under the given key for future runs.
2. Setting Up a Cache Step
Below is a minimal example for a Node.js project. Replace placeholders with your own paths and keys.
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Restore node_modules from cache
- name: Cache node modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
Key points:
pathpoints to the directory you want to cache.- The
keyincludes a hash of the lockfile so the cache invalidates when dependencies change. restore-keyslets the job fall back to a broader key if an exact match isn’t found.
3. Checking Cache Hits and Avoiding Stale Data
GitHub Actions logs expose a cache-hit or cache-miss output. Add a debug step to verify:
- name: Verify cache status
if: steps.cache.outputs.cache-hit == 'true'
run: echo "Cache hit – build will be fast"
When you run the workflow twice, the second run should show cache-hit: true in the logs. If you change the lockfile, the key changes, and a cache miss occurs, forcing a fresh install.
4. Trade‑offs and Limits
- Size limits: Each repository can hold up to 5 GB of cache, and your GitHub account is capped at 10 GB total. Large monorepos may hit these limits and trigger automatic eviction.
- Stale cache risk: If you forget to include version identifiers in the key, a cache hit may deliver an old dependency set, masking regressions.
- Security consideration: Do not cache directories that may contain secrets or sensitive build artifacts. Only cache deterministic, non‑secret outputs.
- Manual invalidation: When you need to purge a cache (e.g., after a large dependency upgrade), delete it via the repository settings or the
actions/cacheCLI. GitHub does not automatically evict caches on dependency changes unless the key changes.
5. Next Steps
- Identify the longest step in your CI pipeline (dependency install, package build, etc.).
- Add a cache step before that step, using a key that includes a hash of the relevant lockfile or version file.
- Run the workflow twice and confirm a cache hit.
- Monitor cache size in the repository settings and adjust key granularity if you hit the 5 GB limit.
- Automate cache cleanup on major version bumps by updating the key pattern.
By following these steps, you’ll shrink CI turnaround times, improve developer experience, and keep your workflow reliable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.