Skip to content

Caching Optimization

GitHub Actions provides robust mechanisms for caching dependencies and artifacts to accelerate build times and reduce resource consumption. By leveraging caching, you can avoid redundant downloads of packages, minimize I/O operations, and ensure consistent environments across workflows. This section explores advanced caching strategies tailored for CI/CD pipelines, focusing on dependency resolution and artifact reuse.


Built-in Caching in GitHub Actions

GitHub Actions includes a built-in caching system for common package managers like npm, pip, and Maven. These caches are automatically managed and optimized for typical workflows. For example:

  • npm: Caches node_modules directory.
  • pip: Caches Python packages installed via pip install.
  • Maven: Caches dependencies from the local repository.

By default, these caches are scoped to the repository and workflow, ensuring that subsequent runs can reuse previously downloaded packages. However, for more granular control or custom use cases, the actions/cache action is recommended.


Using the actions/cache Action

The actions/cache action allows you to manually manage cache keys, paths, and expiration. This is ideal for scenarios where the built-in caching is insufficient or when you need to cache non-standard artifacts. Here's how to use it:

1. Restore Cache

- name: Restore cached dependencies
  uses: actions/cache@v3
  with:
    path: ./node_modules
    key: ${{ hashFiles('package.json') }}

2. Install Dependencies

- name: Install dependencies
  run: npm install

3. Save Cache

- name: Save cache
  uses: actions/cache@v3
  with:
    path: ./node_modules
    key: $${{ hashFiles('package.json') }}
    restore-keys: |
      ${{ hashFiles('package.json') }}

This approach ensures that the cache is only saved if the restore step succeeds, avoiding stale data.


Best Practices for Cache Management

  1. Use Semantic Cache Keys
    Include variables like GITHUB_REF or GITHUB_SHA in your cache key to differentiate between branches or commits:

    key: ${{ hashFiles('package.json') }}-${{ GITHUB_REF }}
    

  2. Limit Cache Size
    GitHub Actions imposes a 10 GB limit per repository. Prioritize caching large or frequently updated dependencies.

  3. Invalidate Caches Strategically
    Update cache keys when dependencies change (e.g., after a package.json update) to avoid using outdated data.

  4. Combine with Dependency Lock Files
    Use package-lock.json or Pipfile.lock to ensure deterministic builds and reduce cache misses.


Example: Caching Dependencies in a Node.js Project

name: Build Node.js Project

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Configure Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      - name: Restore cached dependencies
        uses: actions/cache@v3
        with:
          path: ./node_modules
          key: ${{ hashFiles('package.json') }}-${{ GITHUB_REF }}
      - name: Install dependencies
        run: npm install
      - name: Build project
        run: npm run build

Example: Caching Dependencies in a Python Project

name: Build Python Project

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v3
        with:
          python-version: '3.9'
      - name: Restore cached pip dependencies
        uses: actions/cache@v3
        with:
          path: ./venv
          key: ${{ hashFiles('requirements.txt') }}-${{ GITHUB_REF }}
      - name: Install dependencies
        run: |
          python -m venv venv
          source venv/bin/activate
          pip install -r requirements.txt
      - name: Run tests
        run: python -m pytest

Key takeaways

  • Caching reduces redundant downloads and speeds up builds by reusing dependencies.
  • Use actions/cache for granular control over cache keys, paths, and expiration.
  • Semantic cache keys (e.g., including branch names) ensure correct cache reuse.
  • Combine caching with lock files to maintain build consistency and minimize cache misses.
  • Monitor cache size limits and prioritize caching high-impact dependencies.