Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXLVI · CachingCaching mechanisms

actions/cache@v4 — inputs, behaviour, and a real workflow

Intermediate⏱ ~20 mingit

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

Not yet marked complete on this device.

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 to restore-keys. On either case, the runner saves under key at job end.
  • restore-keys - an optional, ordered list of prefix-match fallbacks. The runner tries each restore-key in order, returning the most recent entry that matches the prefix. If restore-keys is 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:

InputDefaultPurpose
upload-chunk-size32 MB on Windows, 8 MB on LinuxChunk size for the upload; reduce for slow networks
enableCrossOsArchivefalseAllow a Linux-cached entry to be restored on Windows and vice versa
fail-on-cache-missfalseFail the step if no entry matches key exactly (does not consult restore-keys)
lookup-onlyfalseDo 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_modules directory restores in milliseconds. Skip the cache and let the install step do its work.
  • The install is cheap. go build on 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

  1. Always include runner.os in the key. A cache created on ubuntu-latest is not safely restorable on windows-latest even with enableCrossOsArchive: true; the path contents differ.
  2. Use multi-line restore-keys for ordered fallback. A workflow that supports Python 3.11 and 3.12 can list pip-py3.11- first and pip-py3.12- second, restoring the 3.11 cache when the 3.12 cache misses.
  3. Do not set fail-on-cache-miss: true unless the cache is required. The default semantic is best-effort and that is almost always what is wanted.
  4. Audit the path list. A cache that includes ~/.aws or ~/.npmrc leaks 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-keys set to the molecule version prefix.
  • Terraform for Production Sysadmins - Parts IX-XII (State) use actions/cache@v4 for the Terraform provider plugin cache (a cache) and actions/upload-artifact@v4 for the plan file (an artifact).

Quiz

Knowledge check · 4 questions

  1. Q1. Which input of `actions/cache@v4` controls the prefix-match fallback when the exact key is absent?

  2. Q2. The post-job save step runs even when the lookup missed and the install step rebuilt the cache from scratch.

  3. Q3. Name the three required inputs of `actions/cache@v4` and the role of each.

  4. 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.