Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXVIII · CI ArchitectureCI Architecture

Artifacts, caches, and outputs — three distinct mechanisms for moving data through a job

Foundation⏱ ~24 mingit

What you'll learn

  • Distinguish artifacts, caches, and outputs by lifetime, scope, and purpose
  • Identify the correct use case for each mechanism
  • Apply actions/upload-artifact@v4 and actions/download-artifact@v4 to move build outputs between jobs
  • Apply actions/cache@v4 to speed up repeated dependency downloads
  • Recognise the failure modes of treating a cache as an artifact or an artifact as 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 CI job has three distinct mechanisms for moving data through its lifetime, and they are not interchangeable. Artifacts are durable blobs the workflow produces and may need to download later. Caches are reusable payloads the workflow fetches on every run to avoid repeating expensive work. Outputs are structured values a step returns to the job and the workflow. Treating a cache as an artifact wastes storage. Treating an artifact as a cache loses the audit trail. Treating an output as an artifact fails because outputs do not survive the job.

The three mechanisms at a glance

flowchart TB
    subgraph AR["Artifact (durable, audited, downloadable)"]
        A1["terraform plan file"]
        A2["container image tarball"]
        A3["SBOM"]
        A4["ansible-playbook artefact"]
    end
    subgraph CA["Cache (reusable, key-addressable, best-effort)"]
        C1["pip download cache"]
        C2["node_modules"]
        C3["maven local repo"]
        C4["go module cache"]
    end
    subgraph OU["Output (per-job, structured, ephemeral)"]
        O1["image_tag from build step"]
        O2["plan_id from plan step"]
        O3["deploy_sha from deploy step"]
    end
MechanismLifetimeScopePurpose
ArtifactWorkflow run (days to months)Same workflow run, cross-jobMove build outputs to other jobs or humans
CacheDays (key-matched)Same workflow, any future runSkip redundant work on subsequent runs
OutputJob (seconds to minutes)Same job, downstream stepsPass structured values from one step to the next

The three are layered: outputs flow within a job, artifacts flow across jobs in the same workflow, caches flow across runs.

Artifacts: durable, auditable, downloadable

An artifact is a blob the workflow uploads to the control plane’s artifact store. Other jobs in the same workflow can download it; humans with the right permissions can download it. The artifact is bound to the run ID and the commit SHA; it is auditable and it survives the runner:

steps:
  - name: Build plan
    run: terraform plan -out=tfplan
  - name: Upload plan
    uses: actions/upload-artifact@v4
    with:
      name: terraform-plan
      path: tfplan
      retention-days: 30

The actions/upload-artifact@v4 step uploads the tfplan file. Another job in the same workflow can download it with actions/download-artifact@v4:

steps:
  - name: Download plan
    uses: actions/download-artifact@v4
    with:
      name: terraform-plan
      path: ./plan
  - name: Apply plan
    run: terraform apply -input=false ./plan/tfplan

The artifact is the mechanism for moving a terraform plan from the plan job to the apply job. It is also the mechanism for humans to download the plan and review it.

Artifacts are appropriate for: Terraform plan files, container image tarballs, SBOMs and attestations, test reports.

Artifacts are inappropriate for: dependencies that should be cached (a pip cache belongs in actions/cache), ephemeral build outputs that should never leave the runner.

Caches: reusable, key-addressable, best-effort

A cache is a payload keyed by a hash the workflow computes (typically the lockfile’s hash). The runner checks the cache store for a matching key. If present, the runner restores it. If absent, the runner does the work and uploads the result for next time:

steps:
  - name: Cache pip
    uses: actions/cache@v4
    with:
      path: ~/.cache/pip
      key: pip-${ hashFiles('requirements.txt') }
      restore-keys: |
        pip-
  - name: Install dependencies
    run: pip install -r requirements.txt

The cache is best-effort: a missing cache is a cache miss, not a failure. The workflow falls back to doing the work. This is the correct semantic for dependencies: a build that fails because the cache is missing has coupled correctness to performance.

Caches are appropriate for: language dependency caches (pip, npm, maven, go modules, composer, bundle), Docker layer caches for self-hosted runners, build caches that speed up subsequent runs.

Caches are inappropriate for: build outputs that must be present for the job to succeed (the terraform plan file is required for terraform apply; a cache miss would cause the apply to fail), audit-relevant artefacts (SBOMs, signed plans).

Outputs: ephemeral and structured

An output is a structured value a step returns via $GITHUB_OUTPUT. The job’s subsequent steps can read it via ${ steps.step_id.outputs.key }. Outputs do not survive the runner; they do not appear in other workflows; they are not auditable. They are the right mechanism for “step A discovers the image tag, step B uses it” within a single job (covered in detail in lesson XXXVIII-05).

How they interact

flowchart LR
    subgraph J1["Plan job"]
        S1["step: terraform plan"] --> S2["step: write image_tag to $GITHUB_OUTPUT"]
        S1 --> S3["step: upload tfplan as artifact"]
    end
    subgraph J2["Apply job"]
        S4["step: download tfplan artifact"] --> S5["step: terraform apply"]
        S4 --> S6["step: use image_tag output"]
    end
    CACHE["Cache store\n(pip, go modules, maven)"]
    J1 -.miss/hit.-> CACHE
    J2 -.miss/hit.-> CACHE
  • Caches speed up dependency installation in both jobs.
  • Artifacts move the tfplan from the plan job to the apply job.
  • Outputs move the image_tag from the discovery step to the consumption step within each job.

A failure in the artifact store (the tfplan did not upload) breaks the apply job. A failure in the cache store (the pip cache is missing) is a cache miss; the workflow installs from the registry. A failure in the output (the step did not write to $GITHUB_OUTPUT) breaks only the consuming step, only within the same job.

Cache eviction and retention

  • Artifacts are retained for a configurable period (retention-days: 30 is common; maximum is vendor policy, often 90 days). Available for download by anyone with workflow read permission for the entire retention window.
  • Caches are retained until they are evicted. The eviction policy is “least recently used” and is not user-controllable. A cache not accessed in weeks may be evicted. A miss after eviction is non-fatal.

Anything that must be retrievable in six months is an artifact (with appropriate retention) or is stored elsewhere entirely (S3, internal artifact registry, Terraform Cloud’s plan storage). A cache is a performance optimisation, not a record.

Production discipline

  1. Use artifacts for any blob that must be present for a downstream job or human. Set retention-days explicitly; do not rely on the default.
  2. Use caches for any work that is expensive to repeat and safe to skip. Key the cache by the lockfile hash so changes invalidate the cache automatically.
  3. Use outputs for structured values that flow between steps in the same job. Outputs are not storage.
  4. Never put secrets in caches or artifacts. Both stores are accessible to anyone with workflow read permission.
  5. Document which mechanism each path uses. A future maintainer should identify why a value is in an artifact versus a cache versus an output without re-deriving the design.

Cross-course references

  • Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) applies the same three-mechanism model to package builds.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) applies the same model to molecule runs.
  • Terraform for Production Sysadmins - Parts IX-XII (State) apply the strictest version: Terraform Cloud’s plan storage is artifact-like, separate from the dependency cache.

Quiz

Knowledge check · 4 questions

  1. Q1. A pipeline stores its `terraform plan` output in `actions/cache@v4` rather than `actions/upload-artifact@v4`. What is the failure mode?

  2. Q2. Putting AWS credentials in an artifact's metadata is acceptable because the artifact store is encrypted at rest.

  3. Q3. Name the three mechanisms for moving data through a CI job and identify which is best-effort and which is durable and auditable.

  4. Q4. Redesign a pipeline that confuses caches and artifacts so that the plan-and-apply flow is durable, the dependency installation is fast, and no sensitive data crosses into either store.

    Team T's pipeline stores the `terraform plan` output in `actions/cache@v4` keyed by the commit SHA. It stores `pip` dependencies in `actions/upload-artifact@v4` keyed by `requirements.txt`. The apply job downloads the plan from the cache; the install job downloads pip from the artifact. The apply job fails one in four runs because the cache is evicted; the pip artifact grows unbounded across many workflows.

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