GitHub Actions cache vs artifacts, and the rule that separates them
GitHub Actions cache vs artifacts is decided by one question: can the job carry on without the file? A cache is an optimization the job must survive losing, and an artifact is an output something later depends on. Everything else follows from that, including two different storage meters, $0.07 a gigabyte-month against $0.25, and two different lifetimes.

GitHub's own documentation draws the line in one sentence: cache things that do not change often between runs and are expensive to regenerate, and use artifacts for files you want to keep or view after the run, or to pass between jobs. That is correct and it stops short of the parts that decide real arguments, which are what each one costs, how long each one lives, and which other jobs can see it.
This page puts those side by side. The short version is that they are not two settings on one feature: they are two services with different storage rates, different billing models, different expiry rules and different access scopes, and using the wrong one is usually a bill problem or a security problem rather than a speed problem.
Where they differ, in the places that cost money
Start with the meters, because they are the least-known difference and the largest. Actions cache storage is billed at $0.07 per gigabyte-month past an allowance of 10 gigabytes per repository. Artifact storage is billed at $0.25 per gigabyte-month, pooled with GitHub Packages against a plan-wide allowance that is 500 megabytes on Free, 1 gigabyte on Pro, 2 gigabytes on Team and 50 gigabytes on Enterprise Cloud. That is a factor of three and a half on the rate, and two entirely different allowances.
The billing models differ as well, which matters more than the rates on a spiky pipeline. Cache storage is measured by peak usage in each hour, and only the amount above 10 gigabytes is billable. Artifact storage accrues by the gigabyte-hour all month, and deleting an artifact stops future charges without removing the charges already accrued. A build that uploads 12 gigabytes of artifacts on the first of the month and deletes them on the second has still been billed for two days of them.
| Cache | Artifacts | |
|---|---|---|
| Included | 10 GB per repository | Plan-wide, shared with GitHub Packages: 500 MB on Free, 2 GB on Team, 50 GB on Enterprise Cloud |
| Rate above that | $0.07 per GB-month | $0.25 per GB-month |
| How usage is measured | Peak usage per hour, above the 10 GB included | GB-hours accrued all month, converted to GB-months |
| Lifetime | Removed after 7 days without access; oldest evicted first once the repository limit is hit | A retention period you set, defaulting to the repository or organization policy, overridable per upload with retention-days |
| Who can read it | The same branch, the default branch and, on a pull request, the base branch | Anyone with access to the workflow run, and other jobs in the same run |
| If it is missing | The job regenerates the files and carries on | The dependent job fails |
| Deleting the run | Unaffected | All artifacts for that run are deleted with it |
| Rate limits | 200 uploads and 1,500 downloads a minute per repository | Not published as a per-minute cache-style limit |
The rule, and the two ways people break it
A cache must be losable. Anything you put in one has to be something the job can rebuild if the restore misses, because the restore will miss: after seven days without access the entry is gone, and once the repository passes its limit the oldest entries are deleted to make room whether you were finished with them or not.
The first way people break that rule is using a cache to pass a build output to a later job. It appears to work, because within a few minutes of each other the entry is certainly there. Then one Monday the cache is cold, the deploy job restores nothing, and it deploys whatever was in the directory, which is nothing. An artifact would have failed loudly instead.
The second way is the opposite: uploading dependency directories as artifacts to move them between jobs. That works reliably and costs three and a half times as much per gigabyte, accrues by the hour, and eats a plan-wide allowance shared with your package registry. A node_modules directory uploaded on every run of a busy repository is one of the most common ways to be surprised by a storage line.
There is also a security asymmetry that the cost comparison hides. GitHub's own documentation says not to store credentials in a cache path, because anyone who can open a pull request against your repository can read the contents of caches in the base branch. Artifacts are scoped to a run rather than a branch. Neither is a place for secrets, and the cache is the one where that mistake is reachable by a stranger.
What each one prints when it goes wrong
The two failures do not look alike, and that is the useful part. An artifact collision is loud, non-retryable and stops the job; a cache problem is usually a warning that leaves the job green.
The artifact side fails on a name conflict, because an artifact name is unique within a run and uploading the same name twice is an error rather than an overwrite. Matrix jobs that all upload coverage hit this on the second job to finish.
Failed to CreateArtifact: Received non-retryable error: Failed request: (409) Conflict: an artifact with this name already exists on the workflow runThe cache side has a comparable conflict and it is not an error. When two jobs race to save the same key, the toolkit turns the reservation failure into a ReserveCacheError, and the calling action logs it rather than failing. The entry one of them wrote is fine; the other job simply did not write one.
Unable to reserve cache with key node-cache-Linux-x64-d5ea0750, another job may be creating this cache.Choosing, in practice
Dependencies, compiler outputs and downloaded toolchains go in a cache. They are expensive to fetch, cheap to refetch, identical across runs, and nothing downstream breaks if they are missing. Use the setup-* action for your language where one exists, because it picks the directories and writes the key for you.
Build outputs, test reports, coverage files, binaries, screenshots of a failed browser test and logs go in an artifact. Something or someone reads them after the job ends, and a missing one should be a failure rather than a silent empty directory.
The one case that looks like both is a compiled binary you want to test in a later job in the same run. That is an artifact, always, because the later job depends on it existing. If uploading it is slow, reach for retention-days to make it cheap rather than reaching for a cache to make it fast: a one-day retention on a large artifact costs almost nothing under an accrual model, and it keeps the failure loud.
For the caching side in more detail, including what the key is made of and why an entry that exists still does not restore, GitHub Actions caching explained and GitHub Actions cache not restored are the two pages that finish this one.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm # dependencies: losable, so a cache
- run: npm ci && npm run build
- uses: actions/upload-artifact@v7
with:
name: dist-${{ github.sha }} # output: depended on, so an artifact
path: dist
retention-days: 3
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v6
with:
name: dist-${{ github.sha }}
- run: ./deploy.shFrequently asked questions
What is the difference between cache and artifacts in GitHub Actions?
Which is cheaper, GitHub Actions cache or artifact storage?
Can I use a cache to pass files between jobs?
retention-days rather than moving it into a cache.How long do GitHub Actions artifacts and caches last?
retention-days. Deleting a workflow run deletes all of its artifacts immediately.Why does my artifact upload fail with a 409 Conflict?
overwrite input if replacing the earlier one is really what you want. It is not a storage or size limit.Related guides
References
- GitHub Docs: workflow artifacts, including artifacts versus dependency caching (verified 2026-09-21)
- GitHub Actions billing: storage rates, plan allowances and the accrual model (verified 2026-09-21)
- GitHub Docs: dependency caching limits, eviction, scope and rate limits (verified 2026-09-21)
- actions/upload-artifact#769, the 409 Conflict message quoted on this page (verified 2026-09-21)
- GitHub Actions documentation