Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXLVI · CachingFoundations

Cache versus artifact — best-effort acceleration versus durable record

Intermediate⏱ ~20 mingit

What you'll learn

  • Distinguish cache from artifact by lifetime, scope, and purpose
  • Identify the failure mode of using a cache as an artifact (silent eviction, plan drift)
  • Identify the failure mode of using an artifact as a cache (unbounded storage cost, retention mismatch)
  • Apply the rule: anything required for correctness is an artifact; anything optional for performance is a cache

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 and an artifact both move blobs through a CI job, and the mechanisms that move them look superficially similar - upload a blob, address it by name, download it later. The similarity is misleading. The two stores have different lifetimes, different eviction semantics, and different visibility, and treating one as the other is the source of two distinct production failures: silent eviction of required data, and unbounded growth of performance data.

The two stores at a glance

flowchart TB
    subgraph Cache["actions/cache@v4 (best-effort, evictable)"]
        C1["pip cache"]
        C2["node_modules"]
        C3["maven local repo"]
        C4["go module cache"]
    end
    subgraph Artifact["actions/upload-artifact@v4 (durable, audited)"]
        A1["terraform plan file"]
        A2["container image tarball"]
        A3["SBOM"]
        A4["signed build attestation"]
    end
    Cache -. miss/hit, evictable .-> J["Runner job"]
    Artifact -. must be present, retained .-> J

The diagram shows the same shape - a store the runner reads and writes - but the arrows are labelled differently. The cache is connected to the runner with a label that includes “evictable”. The artifact is connected to the runner with a label that includes “retained”. The two labels are the operational difference.

PropertyCache (actions/cache@v4)Artifact (actions/upload-artifact@v4)
PurposeSkip redundant workMove required data between jobs
LifetimeUntil evicted (LRU)retention-days (max 90)
EvictionYes, automaticNo, until retention expires
Failure semanticMiss is non-fatalUpload failure is fatal to consumer
VisibilitySame workflow, future runsSame workflow, all jobs, humans
AuditableNo (no run binding)Yes (bound to run + commit)
Best forDependency install, build cachesPlans, binaries, SBOMs, attestations

The “Failure semantic” row is the operational difference. A cache that fails to restore is a miss; the workflow continues. An artifact that fails to upload is a step failure; downstream jobs that depend on it cannot download it and fail in turn.

Using a cache as an artifact

The most common misconfiguration is to store data in a cache that the workflow actually requires. The pattern looks correct because the upload step succeeds and the download step succeeds - until the cache is evicted.

# WRONG: terraform plan in a cache
- name: Cache plan
  uses: actions/cache@v4
  with:
    path: tfplan
    key: plan-${ github.sha }
- name: Restore plan
  uses: actions/cache@v4
  with:
    path: tfplan
    key: plan-${ github.sha }

The plan is required for the apply step. The cache may be evicted between the cache step and the apply job. If eviction happens, the apply step rebuilds the plan against the current cloud state - and if the cloud state has drifted, the rebuilt plan differs from the cached one. The apply step applies a plan that was never reviewed.

The correct pattern is actions/upload-artifact@v4 with explicit retention-days:

# RIGHT: terraform plan in an artifact
- name: Upload plan
  uses: actions/upload-artifact@v4
  with:
    name: terraform-plan
    path: tfplan
    retention-days: 30
- name: Download plan
  uses: actions/download-artifact@v4
  with:
    name: terraform-plan
    path: ./plan

The artifact is retained for 30 days, is bound to the run ID, and is auditable. The apply step downloads a plan that is the same plan that was reviewed.

Using an artifact as a cache

The opposite misconfiguration is to store performance data in an artifact. The artifact store is durable; the data is supposed to be evictable. The result is unbounded growth.

# WRONG: pip dependencies in an artifact
- name: Upload pip cache
  uses: actions/upload-artifact@v4
  with:
    name: pip-cache
    path: ~/.cache/pip

Every workflow run uploads a fresh pip-cache artifact. The artifact is retained for the configured retention period. After a quarter, the repository has accumulated hundreds of pip caches, each several hundred megabytes, none of which can be evicted because they are artifacts. The storage bill grows linearly with run count.

The correct pattern is actions/cache@v4:

# RIGHT: pip dependencies in a cache
- name: Cache pip
  uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: pip-${ hashFiles('requirements.txt') }
    restore-keys: |
      pip-

The cache store evicts entries under LRU pressure. Pip dependencies that have not been accessed in weeks are evicted automatically. The cache store grows to a bounded size (10 GB per repository by default) and stays there.

How the two interact in a single pipeline

A typical Terraform pipeline uses both stores correctly:

flowchart LR
    subgraph PJ["Plan job"]
        PS["terraform plan"] --> PA["actions/upload-artifact@v4\nterraform-plan"]
    end
    subgraph AJ["Apply job"]
        AD["actions/download-artifact@v4"] --> AS["terraform apply"]
    end
    PA --> AD
    subgraph Cache["Cache store"]
        CI["actions/cache@v4\npip dependencies"]
        CT["actions/cache@v4\nterraform providers"]
    end
    PJ -. miss/hit .-> CI
    AJ -. miss/hit .-> CT

The plan file moves through the artifact store. The dependency caches move through the cache store. The artifact store is durable, audit-bound, retention-bounded. The cache store is best-effort, evictable, performance-only. The two stores do not interfere with each other.

Production discipline

  1. Audit every actions/cache@v4 use. Confirm the cached path is data that the workflow can rebuild from authoritative sources. If it cannot, the path is an artifact.
  2. Audit every actions/upload-artifact@v4 use. Confirm the uploaded path is data that the workflow cannot rebuild (otherwise it should be a cache). Confirm no secrets are included.
  3. Test the failure modes. Delete the cache entry between jobs and confirm the workflow recovers. Delete the artifact upload and confirm the workflow fails (or, if it should recover, switch to a cache).
  4. Document the decision per workflow. A maintainer reading the workflow six months later should identify why a path is in an artifact versus a cache without re-deriving the design.

Cross-course references

  • Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) applies the same distinction to package repositories: /var/cache/apt is a cache (evictable), a Debian package repository mirror is an artifact (durable).
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) applies the distinction to molecule runs: the molecule dependency cache is a cache; the molecule report is an artifact.
  • Terraform for Production Sysadmins - Parts IX-XII (State) apply the strictest version: Terraform Cloud’s plan storage is artifact-like (durable, audited), the provider plugin cache is a cache (best-effort, evictable).

Quiz

Knowledge check · 4 questions

  1. Q1. A Terraform pipeline stores its plan file in `actions/cache@v4` keyed by the commit SHA. The apply job restores from the cache. What is the failure mode?

  2. Q2. Storing pip dependencies in `actions/upload-artifact@v4` is not necessarily acceptable just because `retention-days` is set to a small value.

  3. Q3. State the rule that distinguishes cache use from artifact use, and explain how to test it.

  4. Q4. Redesign a pipeline that puts the terraform plan in a cache and the pip dependencies in an artifact so that the plan survives eviction, the dependencies stay evictable, and storage cost is bounded.

    Team B's pipeline uploads `tfplan` to `actions/cache@v4` keyed by `plan-${ github.sha }`. It uploads `~/.cache/pip` to `actions/upload-artifact@v4` named `pip-cache` with `retention-days: 7`. The apply job restores the plan from the cache; the install job downloads pip from the artifact. The apply job fails once a month when the cache is evicted; the artifact storage grows by 8 GB per quarter despite the 7-day retention because every workflow run uploads a new pip-cache.

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