Chapter 14Lesson 03~175 minutes

Dependency Caching, Cache Keys, Restore Strategies, and Performance: Configuration, Design Patterns, and Trade-Offs

Caching is a design trade-off among latency, correctness, isolation, storage and operational complexity. This lesson compares exact and prefix keys, dependency and build-output caches, setup-action caching and explicit cache actions, and explains when a faster restore increases poisoning or staleness risk.

Cache vs artifactSetup cachingScopeEvictionPoisoning risk

Learning objectives

  • Choose between cache and artifact based on whether the data is acceleration state or evidence/output.
  • Balance exact keys and restore prefixes without allowing fallback state to become the correctness authority.
  • Compare explicit actions/cache configuration with built-in setup-action caching.
  • Separate dependency caches from build-output caches that may encode hidden environment state.
  • Account for branch scope, cross-OS behavior, eviction, storage cost and poisoning risk in production cache design.

1. Start with the question “What happens if this cache disappears?”

If the correct answer is “the job becomes slower but still reconstructs the same result,” the data is a good cache candidate. If the answer is “we lose the only copy of the release binary, report or provenance evidence,” it belongs in an artifact or external records system instead.

This single question prevents the most common cache architecture error: using a key-addressed, evictable performance service as authoritative build storage.

2. Decision table: choose the smallest mechanism that preserves correctness

Choice Prefer when Benefit Main risk / prerequisite
Exact key only restored state must match all correctness inputs closely clear hit semantics; minimal staleness more misses and storage churn
Exact + narrow restore prefix package manager can reconcile older compatible downloads better warm-start rate fallback is stale by definition; install/revalidation must still run
Built-in setup caching standard package manager and conventional lockfile layout less YAML and maintained integration less granular control; understand what the setup action actually caches
Explicit actions/cache custom path/key/split restore-save/lookup-only behavior is needed full control and explicit evidence more configuration to review and maintain
Dependency download cache network downloads are expensive but package manager revalidates current manifest usually safer correctness boundary poisoning still possible; never store credentials
Build-output cache expensive deterministic compilation keyed by every relevant input potentially large speedup hidden compiler/env/path flags can make stale reuse incorrect
Artifact files are run output/evidence or must cross jobs reliably ID/digest/retention model not optimized for cross-run dependency lookup

3. Built-in setup-action caching versus explicit cache actions

Current setup actions can manage common package-manager caches: setup-node for npm/Yarn/pnpm, setup-python for pip/pipenv/Poetry, setup-java for Gradle/Maven, setup-go for Go modules, setup-dotnet for NuGet and others. This is often the most maintainable choice because the action knows the package manager's conventional cache location and lockfile hashing model.

- name: Set up Python with built-in pip caching
  uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
  with:
    python-version: '3.13'
    cache: pip
    cache-dependency-path: requirements.lock

- name: Install from the dependency contract
  run: python -m pip install -r requirements.lock

Use explicit actions/cache when you need a custom path, explicit restore-prefix policy, split restore/save steps, lookup-only, fail-on-cache-miss for a deliberately strict offline flow, or richer cache evidence.

4. Restore prefixes trade hit rate for staleness

Restore keys are ordered prefixes. A narrow fallback such as “same OS + same architecture + same Python minor” is easier to justify for pip downloads than a prefix that ignores toolchain and operating system. Every dimension you remove broadens the set of stale bytes eligible for restoration.

The safe production question is not “Can we get more hits?” but “Can the consumer deterministically reject or repair any incompatible state this prefix may restore?” If not, keep the key exact or redesign the cached path.

5. Build-output caches need stronger key contracts

Compiler outputs can depend on more than source and lockfiles: compiler version, target architecture, feature flags, environment variables, generated headers, SDK versions, system libraries and even absolute paths. A build cache key that omits any of these can produce a fast but invalid result.

Prefer tool-native caches that already model those dependencies where possible. If you cache generated build trees directly, document every correctness-relevant input and periodically prove a clean rebuild produces equivalent results.

6. Scope and cross-OS reuse are explicit compatibility decisions

GitHub cache lookup is constrained by ref/branch scope; default-branch entries can be available to other branches under documented rules. This is a security and isolation boundary, not an inconvenience to bypass. Similarly, cross-OS archives are opt-in and require compatible tools/paths. Do not remove runner.os from a key just to force a hit across incompatible dependency stores.

7. Storage and retention affect reliability and cost

The current default cache inactivity period is 7 days and default storage limit is 10 GB per repository. Eligible GitHub.com repositories can opt into larger retention and storage, which can introduce billing. Very broad keys can create cache thrashing; very specific keys can create thousands of rarely reused entries.

Track cache size, last-access time, hit rate, restore time and install/build savings. Optimize the critical path, not the cache-hit percentage by itself.

8. Worked scenarios

Scenario Choice Prerequisites Evidence that justifies it
Python API service setup-python pip cache keyed by dependency file source-controlled exact dependency input; standard pip cache lock hash + Python version + hit state + install duration
Large C++ build tool-native compiler cache with explicit compiler/flags/arch dimensions deterministic build and validated cache tool clean rebuild equivalence + compiler/flags + cache stats
Release binary artifact, not cache run/SHA/digest/retention provenance artifact ID/digest + source SHA + release promotion record
Fork PR validation restore-only use of safe dependency caches no secrets/executable trusted state inside cache event trust + cache ref/key + dependency revalidation
Monorepo packages package-scoped dependency hashes, possibly setup cache per ecosystem known dependency boundaries changed lockfiles + key expansion + per-package hit/latency

9. Cache rollback means bypass/re-key, not pretending old bytes never existed

If a cache is suspected of poisoning or hidden-environment dependence, preserve its ID/key/version/ref and the failing run first. Then temporarily bypass it, tighten the key or use a new bounded epoch while the cause is investigated. Exact deletion can be appropriate after evidence is captured, but “delete all caches” destroys useful diagnostic state and can create a thundering herd of cold installs.

Knowledge check

When is built-in setup-action caching preferable?

Why is a broader restore prefix not automatically better?

What additional risk does a build-output cache have compared with a download cache?

Should a release binary be kept only in actions/cache?

What is a safer first response to suspected cache poisoning than deleting everything?

Next lesson

Diagnose cache failures without destroying evidence

Lesson 4 engineers stale keys, unsafe contents and hit-assumption bugs and repairs them from observable state.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked on 2026-09-09. Mandatory examples target GitHub.com and ubuntu-24.04. actions/cache v6.1.0 is pinned by full commit SHA and runs on Node 24; keep self-hosted runners current enough to execute Node 24 actions (GitHub documents runner 2.327.1 as the Node 24 minimum in current official action guidance). Cache entries are scoped by key, cache version and ref/branch visibility. Exact primary-key matches report cache-hit == 'true'; prefix/restore-key matches report 'false'; a total miss yields an empty value. The cache action's post-save runs on successful jobs; do not rely on deprecated save-always. GitHub currently defaults cache inactivity retention to 7 days and repository cache storage to 10 GB; eligible repositories/organizations can configure higher limits, so performance/storage assumptions must be recorded rather than hard-coded.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.