Optimizing GitHub Actions Build Times with Smart Caching
Learn how to use actions/cache to reduce CI build times by implementing lockfile hashing and understanding cache key strategies.
07 Mar 2026, 18:06 UTC

Most CI/CD pipelines spend more time downloading the same dependencies than they do running tests. If your workflow spends three minutes fetching node_modules or Maven .m2 packages on every push, you are wasting runner minutes and delaying feedback loops. The solution isn't faster internet, but a properly implemented caching strategy.
The actions/cache action allows you to persist specific directories across workflow runs. By mapping your cache key to your lockfile, you ensure that dependencies are only re-downloaded when your requirements actually change.
The Mechanics of the Cache Key
GitHub Actions caching operates on a key-value system. When a workflow starts, it looks for a cache associated with a specific key. If it finds one, it downloads the files to the runner. If it doesn't, the workflow proceeds without them, and a new cache is uploaded at the end of the run.
The most effective way to define these keys is by using a hash of your lockfile. This ensures that if package-lock.json or go.sum changes, the key changes, triggering a fresh install and a new cache update.
Example: Caching Node.js Dependencies
In this example, we implement caching for a Node.js project. We use the restore-keys feature to provide a fallback; if an exact match for the current lockfile isn't found, GitHub pulls the most recent cache from the main branch to speed up the incremental install.
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
In the configuration above, the path points to the global npm cache directory. The restore-keys allows the runner to download an older version of the cache even if the lockfile hash has changed, which allows npm ci to only download the newly added or changed packages rather than starting from scratch.
Verification and Storage Limits
To verify that your cache is working, navigate to the Workflow tab in your GitHub repository. Click on a specific run and look for the Cache step. You will see either:
- Cache hit: The exact key was found, and files were restored.
- Cache miss: No matching key was found, and a new cache was created at the end of the job.
Keep in mind that caching is not infinite storage. GitHub enforces a 10GB limit per repository. Once you exceed this, GitHub will evict the least recently used (LRU) caches to make room. You can monitor your usage in Settings > Actions > Caches.
Critical Constraints and Risks
Caching is powerful, but it introduces two primary risks. First, never store secrets in cached directories. Caches are stored as compressed archives and are accessible to anyone with read access to the repository. Second, be wary of "poisoned" builds. If a build produces a corrupted dependency that then gets cached, every subsequent build will inherit that corrupted state. If you encounter inexplicable failures after a dependency update, your first step should be to clear the cache via the GitHub UI to force a clean state.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.