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

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):

  1. It looks for an exact match on key.
  2. If there's none, it tries each restore-keys prefix in order and restores the most recent matching cache.
  3. 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

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

References