Git, CI/CD & GitOpsXLVI · CachingOperational hygiene
Cache cost and retention — what caches cost, how they are evicted, and when to delete them
What you'll learn
- Identify the 10 GB per-repository size limit for caches
- Explain LRU eviction and why it is not user-controllable
- Apply `gh cache delete` for explicit cache invalidation
- Recognise the cost model: free for public repositories, included in Actions minutes for private repositories
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
A cache is free in the sense that it costs no storage fee beyond the GitHub Actions subscription, but it is not free in the sense of “infinite and persistent”. The cache store is bounded by a per-repository size limit, is evicted under LRU pressure, and cannot be retained beyond the platform’s policy. Operational hygiene for caches is monitoring the size budget, evicting poisoned entries, and documenting the cache key strategy so that the next maintainer understands why a particular key exists.
The size limit
GitHub imposes a 10 GB per-repository cap on the total cache size. The cap is shared across all branches, all workflows, and all keys in the repository. When the total exceeds 10 GB, the cache store evicts entries under LRU pressure until the total is back below the cap.
The 10 GB cap has three operational consequences:
- Cross-workflow contention. A workflow with a large pip cache (4 GB) and a workflow with a large node_modules cache (4 GB) leave only 2 GB for the rest of the repository. A third workflow that needs a 3 GB cache will evict the older pip or npm entries under pressure.
- No user-controllable priority. The cache store does not expose a way to mark an entry as “evict last”. A critical cache and a transient cache are evicted with the same LRU policy.
- Silent eviction. The cache step reports success on a miss but does not report that an entry was evicted to make room for a newer one. A workflow that previously hit its cache may start missing without any obvious cause.
flowchart LR
A["Workflow A creates pip cache (4 GB)"] --> R["Repository cache store"]
B["Workflow B creates npm cache (4 GB)"] --> R
C["Workflow C creates go cache (3 GB)"] --> R
R --> D{"Total > 10 GB?"}
D -->|yes| E["LRU evict until total < 10 GB"]
D -->|no| F["All entries retained"]
E --> G["Oldest entries removed"]
G --> H["Workflow A misses next run"]
The diagram shows the contention pattern: three workflows each create caches that fit individually, but together they exceed the 10 GB cap. The LRU eviction removes the oldest entries, and the workflows that depend on them start missing.
Eviction and retention
The cache store does not expose a user-controllable retention policy. The retention is governed by three platform-level rules:
- LRU eviction under size pressure. Entries that have not been accessed recently are evicted first when the total size exceeds 10 GB.
- Time-based eviction under platform pressure. GitHub may evict entries that have not been accessed in a long time (typically several weeks) even if the size limit is not reached. The policy is not documented in detail and is not user-controllable.
- No explicit retention-days setting. Unlike artifacts
(
retention-days: 30is a real input onactions/upload-artifact@v4), caches have no retention setting. The only way to force deletion isgh cache delete.
The asymmetry is deliberate. Artifacts are durable records the workflow may need to retrieve later; the retention period is user-controlled and bounded. Caches are performance optimisations; the platform decides when they are no longer useful.
Explicit deletion with gh cache
The gh cache subcommand exposes three operations that are
useful for operational hygiene:
gh cache list- list all cache entries for the current repository, with their keys, sizes, and creation dates.gh cache delete <key>- delete a specific cache entry by key. Used to evict poisoned entries and to invalidate caches after a security incident.gh cache delete --all- delete all cache entries for the current repository. Used to recover from a poisoning event that affected multiple keys.
# List the largest cache entries
gh cache list --sort size --limit 20
# Delete a specific poisoned entry
gh cache delete "pip-$SHA_OF_POISONED_KEY"
# Evict all caches after a security incident
gh cache delete --all
gh cache delete is the operational tool for cache poisoning
and for cache invalidation after a lockfile change that the
existing keys do not track. The gh cache list output is the
input to a size-budget audit: a repository whose largest 20
entries exceed 8 GB is at risk of contention with other caches
in the same repository.
Cost model
The cost model for caches depends on the repository visibility:
- Public repositories. Caches are free. There is no storage fee and no operations fee. The cache size limit (10 GB) still applies.
- Private repositories. Cache storage is included in the GitHub Actions pricing model. There is no separate cache fee, but the cache operations (uploads and downloads) count against the Actions minutes budget for the runner type that performed them.
A workflow that uploads a 1 GB cache on every run, runs 100 times per day, and has a 30-day retention: the 1 GB upload is 100 GB-day per day, or 3 TB-day per month. On a Linux runner, this counts against the Actions minutes budget at the rate specified in the GitHub Actions pricing page.
The cost is rarely the binding constraint. The binding constraint is the 10 GB size cap and the LRU eviction. A repository that hits the size cap evicts entries regardless of whether the team is willing to pay for more storage.
When to evict explicitly
Three situations call for explicit gh cache delete:
- After a poisoning event. A cache entry is known to be
malicious.
gh cache delete <key>removes it; the next run rebuilds from authoritative sources. - After a security incident that affected the workflow. A
compromised step may have written to multiple keys.
gh cache delete --allremoves every entry; the next run rebuilds the entire cache from scratch. - After a lockfile change that the cache key did not track.
A lockfile change that did not invalidate the cache (because
the key was derived from a different file) leaves a stale
entry.
gh cache delete <key>evicts the stale entry; the next run rebuilds against the new lockfile.
The third situation is rare in well-configured workflows but common in misconfigured ones. The defensive pattern is to evict any cache entry that is suspected of staleness, then verify the next run rebuilds from authoritative sources.
Production discipline
- Document the cache-budget per repository. A runbook entry
that lists the cache entries and their sizes, refreshed
quarterly with
gh cache list --sort size. - Audit cache sizes after every workflow addition. A new cache that pushes the total above 8 GB (80% of the cap) is a flag for eviction-policy review.
- Use
gh cache delete --allafter security incidents. A compromised step may have written to multiple keys; the safest response is a full eviction. - Treat the cache as part of the incident-response surface. The cache is a writable store that the workflow trusts by default; a security incident response should include the cache in the scope of forensic and recovery actions.
Cross-course references
- Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) applies the same size-budget and LRU eviction pattern to apt-cacher-ng and yum-cron package caches on self-hosted runners.
- Ansible for Production Sysadmins - Part XXXVII (RepoArch)
applies the same
gh cache deletehygiene to molecule dependency caches, with the cache-budget documented in the collection repository. - Terraform for Production Sysadmins - Parts IX-XII (State) apply the same operational hygiene to the Terraform provider plugin cache, with the cache size limit documented alongside the Terraform version constraint.
Quiz
Knowledge check · 4 questions
Q1. A repository has 12 workflows, each caching 2 GB of dependencies. The total cache size is approaching the platform limit. What happens next?
Q2. The cache retention period is user-controllable via a `retention-days` input on `actions/cache@v4`.
Q3. Name the three situations that call for explicit `gh cache delete` and the command for each.
Q4. Diagnose why a previously fast workflow has started missing its cache on every run and recommend the fix.
Team F's repository had three workflows, each caching 2 GB. The runs were fast (30-second cache restore). Last month, the team added a fourth workflow that caches 4 GB of Docker layer data on self-hosted runners. The three original workflows now miss their caches every run; the cache step reports success but the path is not restored. The fourth workflow's cache is also missing intermittently. The team's `gh cache list --sort size` shows the four caches totalling 11 GB.
Passing score: 75%. Answers are checked in this browser.