GitHub Actions Caching: Speeding Up Your Builds

GitHub Actions Caching: Speeding Up Your Builds
Every time your workflow runs, it starts with a clean virtual machine. This means it has to download all your dependencies—Node modules, Python packages, or Java binaries—from the internet over and over again. GitHub Actions Caching allows you to store these dependencies on GitHub's internal servers and restore them instantly in subsequent runs, slashing your build times from minutes to seconds.
Table of Contents
- The Problem with Ephemeral Runners
- How Caching Works
- The 'actions/cache' Action
- Managing the Cache Lifetime
- Best Practices for Effective Caching
- Frequently Asked Questions
- Key Takeaway
The Problem with Ephemeral Runners
GitHub-hosted runners are ephemeral. This is a great security feature because it ensures every build starts fresh. However, it is a nightmare for performance if you have a large project.
If your npm install takes 3 minutes because it has to fetch 1GB of data from the registry, and you run your tests 50 times a day, you are wasting 150 minutes of your team's time (and your GitHub Actions budget) every single day on redundant network requests.
How Caching Works
GitHub Actions uses a Key-Value store for caching.
- The Key: A unique string (usually a hash of your lockfile, e.g.,
package-lock.json) that identifies the state of your dependencies. - The Value: The actual folder containing the dependencies (e.g.,
node_modules).
When a workflow runs:
- It checks if a cache with that Key already exists.
- If it does (a "Cache Hit"), it downloads the folder directly from GitHub's internal high-speed network.
- If it doesn't (a "Cache Miss"), it runs your install command and saves the resulting folder as a new cache for future runs.
The 'actions/cache' Action
While many language-specific actions (like setup-node or setup-python) have built-in caching, the actions/cache action is the universal tool for managing any custom folder.
Key Breakdown:
path: The folder you want to save.key: The exact identifier. By includinghashFiles('**/package-lock.json'), the cache key will change the moment you add a new dependency, forcing a fresh install.restore-keys: A fallback list. If an exact match isn't found, GitHub will find the most recent cache starting withnode-to give you a partial head start.
Managing the Cache Lifetime
GitHub keeps your caches for 7 days after they were last accessed. If a cache hasn't been used in a week, it is automatically evicted.
There is also a total storage limit: 10GB per repository. If you exceed 10GB, GitHub will start deleting the oldest caches to make room for new ones. You can monitor and manually delete your caches in the Actions > Caches tab of your repository settings.
Best Practices for Effective Caching
- Only cache what is slow to build/download: Don't cache tiny folders; the overhead of downloading the cache might actually be slower than just running the command.
- Include the OS in the Key: A
node_modulesfolder built on Linux will not work if restored on a Windows runner. Always prefix your keys with${{ runner.os }}. - Use the right hash file: Ensure your key is tied to your lockfile (
yarn.lock,Gemfile.lock), not yourpackage.json. The lockfile guarantees that the actual installed versions haven't changed.
Frequently Asked Questions
Does a Cache Miss break the build? No. If the cache is not found, the workflow simply continues to the next step (where you should run your install command). Caching is an optimization, not a dependency.
Can I share caches between different branches?
Yes, but with restrictions. A branch can access caches created on the same branch or the parent branch (usually main). However, a child branch cannot access a cache created on a sibling branch to prevent security vulnerabilities.
Key Takeaway
Caching is the most effective way to optimize your GitHub Actions performance. By intelligently hashing your lockfiles and using the actions/cache action, you transform your CI from a slow network-heavy process into a blazing fast automation suite that reuses its previous hard work.
Read next: Passing Data Between Jobs: Artifacts vs. Caching →
