GitHub Actions Caching & Artifacts
Every GitHub Actions job starts on a fresh runner, which means downloading dependencies, rebuilding containers, and recompiling from scratch every time. Caching saves reusable files, such as package-manager downloads, build caches, and Docker layers, between runs, often cutting minutes off every pipeline. Artifacts are the other half: they pass build outputs between jobs and keep reports, binaries, and logs after a run.
They look similar but serve different purposes. Caches are best-effort speed-ups that may be evicted at any time. Artifacts are explicit outputs your workflow depends on.
TL;DR
- The easiest win: built-in caching in
actions/setup-node,setup-python,setup-go, andsetup-java(cache: npm,cache: pip…). actions/cachecaches arbitrary paths under a key, typically built withhashFiles()of the lockfile.restore-keysgive prefix fallbacks, so a partial cache beats none when the lockfile changes.- Cache downloaded packages, not installed
node_modules, unless you know what you're doing. - Docker builds: use BuildKit's GitHub Actions cache backend (
cache-from/cache-to: type=gha). - Caches are scoped by branch (with fallback to the default branch), limited to about 10 GB per repo, and evicted after 7 days unused. Artifacts pass files between jobs and persist for a retention period.
Quick Example
Core Concepts
Cache Keys and Restore Keys
actions/cache restores at the start of the job and saves at the end (only if there was no exact hit):
- It looks for an exact match on
key. - If there's none, it tries each
restore-keysprefix in order and restores the most recent matching cache. - At job end, if the key missed, it saves the path under
key.
Good keys combine the OS, tool version, and a hash of the files that determine the cache's content:
When the lockfile changes, the exact key misses and a restore-key brings back an older cache. The package manager then only downloads the differences.
What to Cache
Caching the package manager's download cache plus npm ci is robust. Caching node_modules directly is faster but can break when Node versions or OS change; if you do it, include those in the key.
Docker Layer Caching
This stores BuildKit layers in the Actions cache, so unchanged layers, such as the dependency install in a well-ordered Dockerfile, are reused. Registry caching (type=registry,ref=…:buildcache) is an alternative that isn't bound by Actions cache limits.
Cache Scope, Limits, and Eviction
- A run can restore caches created on its own branch, the base branch (for PRs), or the default branch. Caches created on feature branches aren't shared with sibling branches. This prevents cache poisoning from untrusted branches.
- Repositories get 10 GB of cache by default (configurable on paid plans). Beyond that, the least recently used entries are evicted, and entries unused for 7 days are removed.
- Caches are immutable: an existing key can't be overwritten. Change the key to refresh content.
Artifacts
actions/upload-artifact@v4 artifacts are immutable per name within a run, are available immediately to later jobs, and default to 90-day retention (set retention-days lower to save storage).
Best Practices
Start With setup-* Built-In Caching
It's one line and handles keys correctly for the common case. Add actions/cache only for additional directories like build-tool caches.
Use Restore Keys for Partial Hits
Without restore keys, any lockfile change means a completely cold cache. With them, you restore the closest previous cache and download only what changed.
Save Caches From the Default Branch
Because PR branches can read caches from main, keeping main's caches warm (for example by running the workflow on push to main) benefits every PR. Some teams restore-only in PR workflows (actions/cache/restore) and save only on main to avoid filling the quota with per-branch caches.
Measure the Win
Check the "Cache restored" and "Cache saved" logs and the cache size. A 2 GB cache that takes 60 seconds to download may be slower than reinstalling. Cache what's expensive to recreate, not everything.
Common Mistakes
Keys That Never Change or Always Change
Using Caches to Pass Files Between Jobs
Caches may be evicted or not yet saved when a dependent job starts, so pipelines that rely on them are flaky. Use artifacts for anything a later job needs.
Caching Secrets or Credentials
Anything in a cached path is readable by future workflow runs, including runs from other branches that can restore it. Never cache files containing tokens (.npmrc with auth, cloud credentials). See GitHub Actions security.
FAQ
What's the difference between a cache and an artifact?
A cache is a best-effort speed-up reused across workflow runs, which may be evicted at any time. An artifact is an explicit output of a specific run, used to pass files between jobs and to download results like binaries or test reports afterwards.
Why isn't my PR using the cache from another PR?
Caches are branch-scoped. A PR can restore caches from its own branch and its base or default branch, but not from other feature branches. Keep the default branch's caches warm so every PR benefits.
How do I clear a bad cache?
Delete it from the repository's Actions → Caches page, or with gh cache delete <key> (or --all). Alternatively, bump a version prefix in the key (v2-deps-...) so new runs ignore old entries.
Should I cache node_modules?
Usually cache the npm download cache (~/.npm) and run npm ci, which is robust and reasonably fast. Caching node_modules directly skips installation entirely, but it's fragile across Node versions and OS changes and bypasses npm ci's clean-install guarantee. If you do, key on OS, Node version, and lockfile hash.
Related Topics
- GitHub Actions — The platform overview
- GitHub Actions Workflow Syntax — Jobs, steps, and outputs
- GitHub Actions Matrix Builds — Caching per matrix combination
- Dockerfile Best Practices — Layer ordering for cache reuse
- Build Tools — Tool-level incremental caching
- CI/CD — Pipeline performance in general