Your CI workflow takes 8 minutes. On every run, it downloads the same dependencies, rebuilds the same files. Why start from scratch when nothing has changed?
GitHub Actions offers two mechanisms to avoid that waste: the cache and artifacts. They look similar, but they serve very different purposes. Choosing the right mechanism at the right moment can halve the duration of your workflows.
What you will learn
- Tell the cache and the artifacts apart in one sentence
- Pick the right mechanism for your need
- Spot the two classic mistakes that come from confusing them
- Combine cache and artifacts in a single workflow
The difference in one sentence
Cache and artifacts answer two distinct needs. The golden rule holds in two lines, keep it in mind, it settles most cases.
The golden rule
- Cache = reusing files between runs (same workflow, different runs)
- Artifacts = sharing files between jobs (same run, different jobs)
A quick comparison
This table puts the two mechanisms face to face on the criteria that matter when choosing: scope, lifetime, visibility and use cases.
| Aspect | Cache | Artifacts |
|---|---|---|
| Purpose | Speed up repeated builds | Share results |
| Scope | Between runs (a repeated workflow) | Between jobs (the same run) |
| Lifetime | 7 days without access | Configurable (1-90 days) |
| Visible in the UI | No (internal) | Yes (downloadable) |
| Maximum size | 10 GB per repository (total) | 10 GB per artifact |
| Use cases | node_modules, .cache/pip | Builds, reports, binaries |
The decision guide
Start from your goal: do you want to speed up an install, or move a result from one job to the next? Each one leads to a mechanism.
Speeding up the installs
Sharing between jobs
The common situations
Here are the concrete cases that come back the most often, sorted by the mechanism that suits them.
When to use the cache
- Speeding up
npm ci,pip installormvn install - Reusing the dependencies between runs
- Caching downloaded binaries (Terraform, kubectl and so on)
- Speeding up incremental builds
Example: you push 3 consecutive commits, and the dependency cache is reused for all 3 runs.
When to use artifacts
- Sharing the build between the test and deploy jobs
- Keeping the test and coverage reports
- Downloading the binaries from the GitHub interface
- Keeping the debug logs when something fails
Example: a build job creates dist/, and the test and deploy jobs reuse it
without rebuilding.
The mistakes to avoid
Two confusions keep coming back. Recognising them avoids workflows that are unstable or needlessly slow.
Using the cache to share between jobs
# ❌ DOES NOT WORK reliablyjobs: build: steps: - run: npm run build - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: dist/ key: build-${{ github.sha }}
deploy: needs: build steps: - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 # Can fail! The cache is not guaranteedWhy? The cache is designed for runs, not for jobs.
Fix: use an artifact.
Using artifacts for the dependencies
# ❌ Wasteful: you upload node_modules on every run- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: path: node_modules/
# ✅ Use the cache- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: cache: 'npm'Why? Dependencies are reusable between runs, so they belong in the cache.
The complete pattern: cache and artifacts
In a real workflow, you use both:
name: CI/CD
on: push: branches: [main]
# No rights by default: every job asks for the minimumpermissions: {}
jobs: build: runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
# CACHE: speeding up npm ci - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '20' cache: 'npm'
- run: npm ci - run: npm run build
# ARTIFACT: sharing the build - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: dist path: dist/ retention-days: 1
deploy: needs: build runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist
- run: ./deploy.shWhat happens:
- The cache speeds up
npm ci(dependencies reused between runs) - The artifact carries the build over to the deploy job
- You only build once
The "cache" half of that pattern then varies per ecosystem, each one having its own paths and its own traps: Python, Node.js and Java.
Key points
Cache = between runs
For the reusable files that speed up repeated builds.
Artifacts = between jobs
For the produced files you want to share or download.
The two complement each other
An optimised workflow uses the cache AND the artifacts.
Checklist:
- Dependencies go to the cache
- Build outputs go to artifacts
- Sharing between jobs means artifacts plus
needs: - Debug logs go to artifacts with
if: failure()
Next steps
- Runners: introduction: what the machine changes in that arbitration, a persistent disk making the local cache almost free.
- Ephemeral runners: the edge case where the local cache does not exist, and where the artifact becomes the only possible transport again.
- Isolating the runners: why a cache shared across levels of trust is a security problem, not only a performance one.