DEV Community

CiCube for CICube

Posted on • Originally published at cicube.io

Caching Dependencies on GitHub Actions


cicube.io

Introduction

GitHub Actions provide two ways of storing files: caching for things like dependencies and artifacts for the results of a job, such as logs or binaries. Although they sound similar, they are used for different purposes. So, we will use caching to speed up our workflow runs.

Another thing to know is that cache access will be restricted to only a few branches: the current branch, the base branch for pull requests, and the default branch. Caches created in unrelated branches won't be available, but that's not something that will affect us in most cases, since we usually operate on either current or base branches.

Proper use of caching can help reduce build times, especially for projects whose dependencies do not change very often.

Choosing the Right Cache Key

We can create metadata-based cache keys, such as OS or commit hashes, that let us only reuse dependencies when absolutely needed. We can also make use of restore keys to get near matches for caches that help minimize rebuild times.

Generally speaking, for such workflows running on multi-OS environments, it is also a good practice to isolate the caches per-OS to avoid cross-platform unnecessary rebuilds. Sometimes we might even use temporary caches valid for only one run, which is useful when we do not need long-term reuse.

- uses: actions/cache@v4
  with:
    path: path/to/dependencies
    # generate a cache key based on the hash of lockfiles
    key: cache-${{ hashFiles('**/lockfiles') }}

- uses: actions/cache@v4
  with:
    path: path/to/dependencies
    key: cache-${{ hashFiles('**/lockfiles') }}
    # restore keys for closest matches. This minimizes the time spent downloading newer dependencies
    restore-keys: |
      cache-npm-

- uses: actions/cache@v4
  with:
    path: path/to/dependencies
    #key: cache-${{ hashFiles('**/lockfiles') }}
    # OS-specific caches to avoid unnecessary rebuilds across different platforms
    key: ${{ runner.os }}-cache
    restore-keys: |
      cache-npm-


- uses: actions/cache@v4
  with:
    path: path/to/dependencies
    #key: cache-${{ hashFiles('**/lockfiles') }}
    #key: ${{ runner.os }}-cache
    # short-lived caches for one-time use
    key: cache-${{ github.run_id }}-${{ github.run_attempt }}
    restore-keys: |
      cache-npm-
Enter fullscreen mode Exit fullscreen mode

We can also share caches across jobs, centralizing cache creation to save time, and ensure caches are saved even if a build fails by using the always() condition.

# Centralized cache reuse

- id: cache-primes-save 
  uses: actions/cache/save@v4 # Save cache 
  with:
    key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}

- uses: actions/cache/restore@v4 # Restore cache 
  with:
    key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}

# Save cache after build failure
- uses: actions/cache@v4
  with:
    path: path/to/dependencies
    key: cache-${{ hashFiles('**/lockfiles') }}
    if: always()
Enter fullscreen mode Exit fullscreen mode

GitHub Actions Package Manager Caching Examples

I've assembled some sample caching for the various package managers in use: npm, pip, Maven, and NuGet. The following configuration should avoid the redownload of dependencies in each workflow run. For example, we do not cache node_modules in the case of npm in order to avoid the case where different Node versions will create problems.

Instead, we cache npm itself dynamically. Pip's cache must be different depending on the OS, and we do the same on Maven and NuGet for their respective repository paths and lock files.

# Cache npm dependencies
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

# Cache pip dependencies
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}

# Cache Maven dependencies
- uses: actions/cache@v4
  with:
    path: ~/.m2/repository
    key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

# Cache NuGet dependencies
- uses: actions/cache@v4
  with:
    path: ~/.nuget/packages
    key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
    restore-keys: ${{ runner.os }}-nuget-
Enter fullscreen mode Exit fullscreen mode

Conclusion

Caching in GitHub Actions means how to optimize the continuous integration workflow by basically avoiding duplicate tasks, for example, re-downloading the dependencies. Carefully selecting metadata, operating system, or lock file-based cache keys, finding close matches with restore keys, and keeping the number less will highly reduce build times. Keep separate caches at different operating systems and use transient caches for one-time build.

Top comments (0)