Caching dependencies and sharing artifacts

GitHub Actions · Practical · 12 min

Caching often halves CI time, and artifacts are the only way to pass build output between jobs.

Caches and artifacts both store files, for different purposes.

A cache is an optimization: dependencies that can be recreated if missing. actions/cache saves a path under a key at the end of a job and restores it at the start of later runs. Build the key from a hash of the lockfile, ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}, so it changes when dependencies change. restore-keys lists prefixes to fall back to a partial match. Easier still, most setup-* actions have a built-in cache: input. Caches are scoped to a branch with fallback to the default branch, evicted after 7 days unused, and limited in total size per repository.

An artifact is an output: build results, test reports, coverage. actions/upload-artifact stores files with the run; actions/download-artifact fetches them in a later job of the same run, and people can download them from the run page. Set retention-days to control storage costs. Since v4, artifact names must be unique within a run, so matrix jobs should include the matrix values in the name.

Rule of thumb: if losing it only makes the build slower, it's a cache; if a later job or a human needs it, it's an artifact.

Example

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with: {node-version: 22, cache: npm}   # caches ~/.npm by lockfile hash
      - run: npm ci && npm run build
      - uses: actions/upload-artifact@v7
        with:
          name: dist
          path: dist/
          retention-days: 5
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v8
        with: {name: dist, path: dist}
      - run: ls dist          # index.html assets/ ...

Remember

  • Cache = recreatable dependencies keyed by a lockfile hash
  • Artifact = outputs passed to later jobs or people
  • setup-* actions have a built-in cache input

Watch out for

  • Caching node_modules instead of the package manager's download cache, which breaks across Node versions.

Check yourself

  1. Which belongs in an artifact rather than a cache?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows

Builds on

Unlocks

Nothing builds on this yet.