A CI job often downloads the same dependencies repeatedly and produces a report that another job needs. Both involve files, but they need different storage contracts. A cache is an optimization: the job should still work when it misses. An artifact is a named result: a consumer expects to retrieve the output of a particular run. This lesson demonstrates both in one complete workflow without requiring application dependencies. Its pinned action versions target GitHub.com and current hosted runners; verify action and runner compatibility before adapting the workflow to a self-hosted runner or GitHub Enterprise Server.
Choose storage by the consumer's expectation
Use a dependency cache for content that can be recreated and whose reuse saves time. A package-manager download cache is a common example. The installation command still validates and assembles the project's dependencies from its lockfile; restoring download data does not prove the application is installed correctly. Measure actual time saved and avoid caching every directory just because it is large. Some build outputs contain absolute paths or platform-specific state that makes reuse fragile.
Use an artifact for a produced report, archive or build result whose identity matters to a later job or reviewer. Give it a clear name and record what generated it. A consumer should fail clearly if an essential artifact is missing, rather than silently substituting a stale cache. Artifacts also have retention and access behavior, so choose a lifetime appropriate to the review or release process. Neither a cache nor an expiring workflow artifact automatically replaces the durable release storage your product may need.
Design a key that represents compatibility
A cache key should include the factors that make its files reusable: operating system, tool or dependency format, relevant version and a hash of the lockfile when applicable. For a real checked-out Node project, an OS-and-lockfile key can prevent reuse across incompatible dependency sets. Confirm that the lockfile exists before depending on its hash; a missing file should not quietly collapse many different projects into the same key.
Restore prefixes broaden the fallback search and may return an older compatible-looking cache. Treat that result as a starting point, not proof that the exact dependency graph is ready. The cache-hit output represents exact-key matching, and a miss or prefix match should still lead to the necessary preparation. Test a cold run deliberately. A workflow that succeeds only when someone has warmed its cache has turned an optimization into an undocumented prerequisite.
Make artifact handoff explicit
The producing job writes a report into a bounded directory and uploads it under a named artifact. A consuming job depends on the producer and downloads that name into an explicit destination. That connection is independent of dependency caching. For matrix builds, use distinct names or a deliberate aggregation process instead of having every job race to upload the same artifact name. Modern artifact action behavior does not mean multiple unrelated uploads become one mutable shared directory.
Check missing-file behavior, retention and what files are selected. An overly broad upload path can include credentials, temporary configuration or unrelated large content. Keep report files in a dedicated directory and inspect their contents. Hidden files and permissions have documented behavior that may matter for packaging; when executable bits or exact archive metadata matter, create an archive with the required properties before upload. For a release, preserve the verified artifact identity rather than rebuilding different bytes later without explanation.
Account for trust and compatibility
Caches are not secret storage. Eligible workflows and pull requests can access caches according to documented scope, and an attacker-controlled contribution may influence files or keys within its allowed context. Do not put tokens, private configuration or signing keys into cached directories. If a privileged job consumes an artifact from a less-trusted workflow, validate its origin and contents and avoid executing it merely because it has a familiar filename.
Action versions and platform support evolve. The complete example pins reviewed commits for cache6.1.0, upload-artifact7.0.2 and download-artifact8.0.2, checked on 11 October 2026. Review their release notes when updating, particularly runtime and self-hosted runner requirements. GitHub Enterprise Server support can differ from GitHub.com for artifact actions. Keep the version assumptions with the workflow and use your platform's supported actions rather than replacing pins with mutable latest references.
Sources and prerequisites
Review Actions, releases and security. Consult the official cache reference, artifact tutorial, cache action, upload action and download action.
GitHub Actions Dependency Caches and Workflow Artifacts Summary
- Recreate cache inputs on misses and verify a cold build.
- Use explicit artifact names and dependencies for result handoff.
- Design compatible keys and review storage trust, retention and action versions.
GitHub Actions Dependency Caches and Workflow Artifacts Examples
Each example shows the code, the output it produces and what each part does.
Cache recreatable input and transfer a named report
Save this complete file as .github/workflows/storage-demo.yml and dispatch it on GitHub.com. The tiny demo-tool file stands in for recreatable cached input, not a real package installation. The consume job's expected final line is shown; cache and upload progress varies between runs.
The cache can be missing without breaking the workflow because the input is recreated. The consumer retrieves this run's named report through the artifact service. The output is a documented expected Actions result, not a claim that the site's local build executed these hosted services.
name: Storage demo
on: workflow_dispatch
permissions: {}
jobs:
produce:
runs-on: ubuntu-latest
steps:
- id: cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .demo-cache
key: ${{ runner.os }}-demo-tool-v1
- name: Recreate input on a non-exact hit
if: steps.cache.outputs.cache-hit != 'true'
run: |
mkdir -p .demo-cache
printf 'demo input\n' > .demo-cache/tool.txt
- name: Produce report
run: |
test -f .demo-cache/tool.txt
mkdir -p report
printf 'report ready\n' > report/result.txt
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: demonstration-report
path: report/result.txt
if-no-files-found: error
retention-days: 7
consume:
needs: produce
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: demonstration-report
path: received
- run: cat received/result.txtreport readyGitHub Actions Dependency Caches and Workflow Artifacts in Real Projects
Derive a cache key from lockfile content
Run this standalone Node.js key-design exercise. It uses the lockfile text as a small fixture and shows why changed dependency input should change the exact key. This models key identity without contacting GitHub's cache service.
const { createHash } = require('node:crypto');
const key = lock => 'Linux-demo-' + createHash('sha256').update(lock).digest('hex');
const oldKey = key('{"dependency":"1.0.0"}');
const sameKey = key('{"dependency":"1.0.0"}');
const newKey = key('{"dependency":"2.0.0"}');
console.log('unchanged input:', oldKey === sameKey);
console.log('changed input:', oldKey === newKey);
const report = { artifactName: 'demonstration-report', run: 42 };
console.log('report identity:', report.artifactName, report.run);unchanged input: true
changed input: false
report identity: demonstration-report 42The dependency content determines the cache key, while the produced report belongs to a named run result. Do not derive the report you consume merely by taking whichever dependency cache happens to restore successfully.
GitHub Actions Dependency Caches and Workflow Artifacts FAQ
Should a cache miss fail my build?
Normally the job should recreate the cached input and continue. A dependency cache exists to save time, not to become an invisible required artifact. Test a cold run and keep the normal installation or generation process intact. Specialized workflows may intentionally require a prebuilt cache, but that is a separate documented contract. Do not let an accidental warm-cache dependency define whether the project can build from its declared sources.
Why did cache-hit not report true after files were restored?
The output distinguishes an exact primary-key match from other outcomes such as a fallback restoration. Review the selected key, cache scope and action version. A restored prefix match may supply useful old download data while still needing preparation for the current lockfile. Use the documented output comparison and complete the installation or generation step when required. File presence alone does not prove exact compatibility with today's inputs.
Can I upload every matrix result under one artifact name?
Plan unique names or explicit aggregation. Modern artifact upload behavior does not provide a general shared mutable directory that every matrix job can append to indiscriminately. Include the platform or matrix dimension in result names, then let a dependent job download and combine them deliberately. This keeps provenance and missing-result checks visible. Consult the pinned action's behavior before relying on overwrites or multiple uploads with a repeated name.
Is a workflow artifact a permanent release archive?
Not automatically. Workflow artifacts have retention and access rules, and a useful review result may expire. For a durable release, promote the verified bytes through the repository's release or package process and preserve their identity and provenance. Keep caches for recreatable inputs and artifacts for explicit run results. Decide storage lifetime based on the consumer's needs rather than assuming every uploaded file is retained indefinitely.
GitHub Actions Dependency Caches and Workflow Artifacts Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
In the practice program, why is changed input: false printed?
GitHub Actions Dependency Caches and Workflow Artifacts Quiz
2 questions to check what you learned.
Which storage is intended for the report produced by a particular run?
- A named workflow artifact
- An arbitrary dependency cache hit
- The runner's temporary memory after the job ends
What should you avoid placing in a dependency cache?
- Recreatable public package download data
- Credentials or private signing material
- A documented compatible tool cache