Every time a GitHub Actions workflow runs without caching, it downloads all dependencies from scratch. For projects with many packages, this can add minutes to every build - multiplied across dozens of pull requests each day, this creates real cost and developer wait time.
Caching dependencies between runs means the packages are only downloaded when the lock file changes, which dramatically reduces workflow duration and egress costs.
The simplest approach is to use the cache input built into the official language setup actions. This is the recommended method because GitHub manages the cache key automatically using the relevant lock file.
Node.js (npm or pnpm):
- uses: actions/setup-node@v4with:node-version: '22'cache: 'npm'
Use cache: 'pnpm' for pnpm projects, or cache: 'yarn' for Yarn. The action automatically hashes the lock file and invalidates the cache when dependencies change.
Python (pip):
- uses: actions/setup-python@v5with:python-version: '3.12'cache: 'pip'
Go:
- uses: actions/setup-go@v5with:go-version: '1.22'cache: true
The actions/setup-dotnet action does not include built-in caching, so use actions/cache directly with a key based on the lock file or project files.
steps:- uses: actions/checkout@v4- uses: actions/setup-dotnet@v4with:dotnet-version: '9.0.x'- run: dotnet restore- run: dotnet build
steps:- uses: actions/checkout@v4- uses: actions/setup-dotnet@v4with:dotnet-version: '9.0.x'- uses: actions/cache@v4with:path: ~/.nuget/packageskey: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/packages.lock.json') }}restore-keys: |${{ runner.os }}-nuget-- run: dotnet restore- run: dotnet build
The restore-keys field allows partial cache hits - if the exact key is not found, GitHub falls back to the most recent cache matching the prefix. This ensures a warm cache even when a single package version changes.
hashFiles on package-lock.json, pnpm-lock.yaml, packages.lock.json, or requirements.txt to get automatic invalidation${{ runner.os }}actions/cache before npm install, dotnet restore, or equivalentAfter adding caching, open a completed workflow run in GitHub and look for the Cache step output. It reports whether the cache was restored (Cache restored) or missed (Cache not found). A high miss rate usually means the key is too specific - broaden the restore-keys pattern.