Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXLVI · CachingFoundations

What a cache is — a content-addressed, key-matched, best-effort blob store

Intermediate⏱ ~18 mingit

What you'll learn

  • Define a cache as a key-addressed, content-derived, best-effort blob store
  • Identify the role of `hashFiles()` in deriving a cache key from a lockfile
  • Distinguish exact-match keys from prefix-match fallback keys
  • Recognise the scope of a cache (repository, branch, workflow) and why the scope 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.

A cache is a key-addressed blob store the runner reads and writes to skip redundant work. The same bytes the runner would otherwise rebuild - language dependencies, compiled objects, downloaded tooling - are uploaded once, addressed by a hash of the inputs that produced them, and restored on the next run when the same inputs reappear. The cache is not durable storage; it is a performance optimisation that survives only as long as the eviction policy allows.

The cache has three properties

A cache, as exposed by actions/cache@v4, has three properties that distinguish it from every other data mechanism in a CI job:

  • Content-addressed. The key is a hash of the inputs the cache depends on. A cache keyed by hashFiles('requirements.txt') changes whenever requirements.txt changes. Two identical inputs produce the same key, no matter who runs the workflow.
  • Best-effort. A cache miss is not an error. If the lookup fails - the key has no entry, the cache was evicted, the store is unavailable - the workflow continues and does the work. Coupling correctness to a cache is a misconfiguration.
  • Scoped. The cache is addressed by repo + branch + workflow + key. A cache entry created on main is not visible from a feature branch with the same key. Two workflows in the same repository cannot collide on the same key.
flowchart LR
    A["requirements.txt"] --> B["hashFiles()"]
    B --> C["key: pip-<sha256>"]
    C --> D{"Cache lookup"}
    D -->|hit| E["Restore path"]
    D -->|miss| F["Run install"]
    F --> G["Save post-job"]
    G --> H["Cache entry created"]

The diagram shows the round trip: the key is derived from the inputs, the runner looks the key up, a hit restores the path, a miss runs the work and uploads the result at job end. The upload at job end is implicit - actions/cache@v4 registers a post-job step that uploads when the job exits successfully.

Why the key is a hash

The cache key is a hash rather than a fixed string because the cache must invalidate itself when the inputs change. A cache keyed by the literal string pip would never invalidate: every run would restore the same payload, even if requirements.txt had changed underneath it. A cache keyed by the SHA of requirements.txt changes the moment any byte of requirements.txt changes, the lookup misses, and the runner rebuilds.

- name: Cache pip
  uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: pip-${ hashFiles('requirements.txt') }

The expression hashFiles('requirements.txt') evaluates to a SHA-256 of the file’s bytes. The pip- prefix is a namespace that lets multiple caches coexist in the same repository (pip-..., npm-..., go-...). The prefix has no semantic meaning to the cache; it is purely a namespacing convention to prevent collisions between different language ecosystems in the same workflow.

Scope: where the cache is visible

A cache is scoped to repository + branch + workflow + key. The scope has three operational consequences:

  • Branch isolation. A cache created on main is not visible from a feature branch. A pull request from feature-x cannot read or overwrite main’s cache for the same key. This isolation prevents cross-branch contamination but also means a feature branch has to rebuild from scratch on its first run.
  • Workflow isolation. A cache key created by the build workflow is not visible from the lint workflow, even on the same branch, unless the key collides. Different workflows can safely use the same prefix without interfering.
  • Cross-PR isolation. GitHub enforces additional scope at the pull-request level: a PR from a fork cannot read caches created by the upstream repository. This isolation is a security control - it prevents a malicious fork from restoring a cache payload it did not create.

What goes into a cache

Caches are appropriate for work that is expensive to repeat and safe to skip:

  • Language dependency caches: pip, npm, yarn, maven, gradle, go modules, composer, bundle, cargo.
  • Build caches: TypeScript tsc incremental builds, Bazel remote-cache mirrors, webpack persistent caches.
  • Tool downloads: large CLIs the workflow installs on every run.
  • Docker layer caches on self-hosted runners (covered in Part XL).

Caches are inappropriate for anything that must be present for the job to succeed (a terraform plan file must be an artifact, not a cache), anything auditable (SBOMs, signed plans must be artifacts), and anything sensitive (neither caches nor artifacts are secrets managers).

Production discipline

  1. Always key a cache by the lockfile hash. A cache keyed by a fixed string never invalidates and silently serves stale bytes after the lockfile has changed.
  2. Treat a cache miss as expected. The first run on a new branch, the first run after a long-eviction window, and the first run after the cache store is unavailable are all misses. The workflow must succeed in all three.
  3. Scope the key prefix deliberately. A prefix like pip-${ hashFiles('requirements.txt') } lets multiple ecosystems share a repository’s cache namespace without collision.
  4. Never put secrets in a cache path. A cache is readable by anyone with workflow read permission; credentials belong in the secrets manager.

Cross-course references

  • Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) discusses the package cache (/var/cache/apt, /var/cache/dnf) that is the operating-system analogue of the CI cache.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) applies the same content-addressing principle to molecule dependency caches.
  • Terraform for Production Sysadmins - Parts IX-XII (State) distinguish the Terraform provider plugin cache (a cache) from Terraform state (an artifact with retention).

Quiz

Knowledge check · 4 questions

  1. Q1. A workflow keys its pip cache with the literal string `pip` rather than `hashFiles('requirements.txt')`. What failure mode follows?

  2. Q2. A cache miss in `actions/cache@v4` causes the workflow step to fail by default.

  3. Q3. Name the three properties of a cache exposed by `actions/cache@v4` and explain why content-addressing matters.

  4. Q4. Diagnose why a pipeline restores stale dependencies despite using `actions/cache@v4` and recommend the fix.

    Team A's `pip install` step restores a cache entry on every run. The dependencies are six months old; a CVE was disclosed in the lockfile eight months ago and the lockfile was updated to a patched version. The CI is green, but production services are running vulnerable package versions. The team assumed the cache was correct because the step succeeded.

Passing score: 75%. Answers are checked in this browser.