Git, CI/CD & GitOpsXLVI · CachingCaching mechanisms
actions/cache@v4 — inputs, behaviour, and a real workflow
What you'll learn
- Identify the three required inputs of `actions/cache@v4` (path, key, restore-keys)
- Distinguish the lookup step from the implicit post-job save step
- Apply `actions/cache@v4` to a real workflow with `hashFiles()` for the key and a prefix for restore-keys
- Recognise the cross-OS, chunk-size, and lookup-only options and when each matters
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
actions/cache@v4 is the standard step for caching dependencies
in a GitHub Actions workflow. It performs two operations: a lookup
that restores a path when a matching key is found, and an
implicit save that uploads the path at the end of the job when the
key has changed (or no entry existed). The two operations happen
in the same step invocation - one step call covers both - and the
behaviour is controlled by three required inputs and several
optional ones.
The required inputs
The step has three required inputs that determine its behaviour:
path- one or more filesystem paths the cache covers. On a hit, the runner restores the entries under these paths before the step finishes. On a miss, the runner saves the entries under these paths at job end. Multiple paths are specified as a multi-line string.key- the exact-match identifier. The runner computes the hash expression, looks up an entry with that exact key, and restores on a hit. On a miss, the runner falls back torestore-keys. On either case, the runner saves underkeyat job end.restore-keys- an optional, ordered list of prefix-match fallbacks. The runner tries eachrestore-keyin order, returning the most recent entry that matches the prefix. Ifrestore-keysis unset, the miss is a complete miss and no fallback is performed.
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${ hashFiles('requirements.txt') }
restore-keys: |
pip-
The example caches ~/.cache/pip. The primary key is
pip-<sha256-of-requirements.txt>. The fallback key is the
prefix pip-, which matches any previously cached pip entry on
the same branch. On an exact hit, the runner restores the exact
entry. On a miss with a prefix match, the runner restores the
most recent entry with the pip- prefix.
Optional inputs worth knowing
Five optional inputs change the behaviour of actions/cache@v4
in ways that matter for production workflows:
| Input | Default | Purpose |
|---|---|---|
upload-chunk-size | 32 MB on Windows, 8 MB on Linux | Chunk size for the upload; reduce for slow networks |
enableCrossOsArchive | false | Allow a Linux-cached entry to be restored on Windows and vice versa |
fail-on-cache-miss | false | Fail the step if no entry matches key exactly (does not consult restore-keys) |
lookup-only | false | Do not save at job end; use for read-only cache reads |
cache-version | (none) | Internal versioning; rarely set explicitly |
fail-on-cache-miss: true is a sharp tool. It changes the cache
semantic from “best-effort” to “required” - the step fails if the
exact key is absent, regardless of restore-keys. This is
appropriate only when the workflow cannot proceed without the
cached data, which is rare. Most use cases should leave the
default (false) and rely on restore-keys for partial-match
fallback.
enableCrossOsArchive: true matters for matrix builds that span
operating systems. A cache entry created on ubuntu-latest can
be restored on windows-latest if the entries are
operating-system-neutral (Python wheels, Go binaries). Native
binaries (compiled C extensions, kernel modules) should not be
cross-restored even with this flag set.
A real workflow
A working Python pipeline with pip caching looks like this:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${ runner.os }-${ hashFiles('requirements.txt', 'requirements-dev.txt') }
restore-keys: |
pip-${ runner.os }-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-dev.txt
- name: Run tests
run: pytest
The actions/setup-python@v5 step with cache: 'pip' is a
convenience that internally invokes actions/cache@v4 with
sensible defaults. The explicit actions/cache@v4 step is shown
for clarity. The key includes runner.os so that
ubuntu-latest and windows-latest runs do not share caches.
The restore-keys prefix includes the same runner.os so the
fallback does not cross OS boundaries.
flowchart LR
A["hashFiles(requirements*.txt)"] --> B["Exact key"]
B --> C{"Hit?"}
C -->|yes| D["Restore ~/.cache/pip"]
C -->|no| E["Try restore-keys prefix"]
E --> F{"Match?"}
F -->|yes| G["Restore most-recent prefix match"]
F -->|no| H["Run pip install (full rebuild)"]
D --> I["Job runs"]
G --> I
H --> I
I --> J["Post-job: save under exact key"]
The diagram shows the resolution flow. The exact key is tried
first. On a hit, the path is restored and the job proceeds. On a
miss, the restore-keys prefixes are tried in order. On a prefix
match, the most recent matching entry is restored. On a complete
miss, the install step runs from scratch. The post-job save runs
in all cases.
When to skip the cache step
Three situations make actions/cache@v4 unnecessary or
counterproductive:
- The path is small and rarely changes. A cache step costs a
download round-trip; a 100 KB
node_modulesdirectory restores in milliseconds. Skip the cache and let the install step do its work. - The install is cheap.
go buildon a small module is faster than the cache download. Skip the cache. - The path includes secrets. The cache store is readable by anyone with workflow read permission. Use a secrets manager, not a cache.
Production discipline
- Always include
runner.osin the key. A cache created onubuntu-latestis not safely restorable onwindows-latesteven withenableCrossOsArchive: true; the path contents differ. - Use multi-line
restore-keysfor ordered fallback. A workflow that supports Python 3.11 and 3.12 can listpip-py3.11-first andpip-py3.12-second, restoring the 3.11 cache when the 3.12 cache misses. - Do not set
fail-on-cache-miss: trueunless the cache is required. The default semantic is best-effort and that is almost always what is wanted. - Audit the path list. A cache that includes
~/.awsor~/.npmrcleaks credentials. Use a narrow path list.
Cross-course references
- Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) applies the same step to apt-cacher-ng style caches on self-hosted runners.
- Ansible for Production Sysadmins - Part XXXVII (RepoArch)
applies the same step to molecule dependency caches, with
restore-keysset to the molecule version prefix. - Terraform for Production Sysadmins - Parts IX-XII (State)
use
actions/cache@v4for the Terraform provider plugin cache (a cache) andactions/upload-artifact@v4for the plan file (an artifact).
Quiz
Knowledge check · 4 questions
Q1. Which input of `actions/cache@v4` controls the prefix-match fallback when the exact key is absent?
Q2. The post-job save step runs even when the lookup missed and the install step rebuilt the cache from scratch.
Q3. Name the three required inputs of `actions/cache@v4` and the role of each.
Q4. Diagnose why a workflow restores an empty cache on every run and recommend the fix.
Team C's workflow runs `actions/cache@v4` with `path: ~/.cache/pip` and `key: pip-cache`. There is no `restore-keys`. The step reports `Cache hit for the key ...` on every run, but the cache contents are empty - `pip install` still runs against the registry and downloads every dependency. The install step takes 4 minutes; the team expected a 30-second restore.
Passing score: 75%. Answers are checked in this browser.