Skip to content
compiler.dev

Cache pip Dependencies in GitHub Actions

Last updated

To cache pip in GitHub Actions, set cache: pip on actions/setup-python. It caches pip's global download cache, keyed on your requirements files. If you can switch to uv, astral-sh/setup-uv caches its own store and installs much faster. Both recipes are below.

Option 1: setup-python built-in cache

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v7
        with:
          python-version: "3.13"
          cache: pip
      - run: pip install -r requirements.txt
      - run: pytest

Per the setup-python README (checked October 2026), cache: pip caches the global pip cache directory. By default the key hashes requirements.txt or pyproject.toml at the repo root. When you have several requirements files, name them:

- uses: actions/setup-python@v7
  with:
    python-version: "3.13"
    cache: pip
    cache-dependency-path: |
      requirements.txt
      requirements-dev.txt

Caching is off unless you set cache. The key also includes the Python version and OS, so a matrix builds one entry per cell.

Option 2: explicit actions/cache

- uses: actions/setup-python@v7
  with:
    python-version: "3.13"
- name: Get pip cache dir
  id: pip-cache
  run: echo "dir=$(pip cache dir)" >> "$GITHUB_OUTPUT"
- uses: actions/cache@v6
  with:
    path: ${{ steps.pip-cache.outputs.dir }}
    key: pip-${{ runner.os }}-py3.13-${{ hashFiles('requirements*.txt') }}
    restore-keys: |
      pip-${{ runner.os }}-py3.13-
- run: pip install -r requirements.txt

pip cache dir returns the right path per OS (~/.cache/pip on Linux). The Python version belongs in the key because compiled wheels are tagged by interpreter.

What pip actually caches

pip stores downloaded wheels and, when it builds a package from source, the wheel it built. A cache hit on a source-only package such as an old C extension can save more than the download: it skips the compile.

CacheWorth itWhy
pip cache dirYesSkips downloads and local wheel builds
A virtualenv (.venv)RarelyTied to one Python build and path; breaks on minor upgrades
~/.cache/pre-commitYes, separatelyHook environments, keyed on .pre-commit-config.yaml
.pytest_cache, .mypy_cache, .ruff_cacheSometimesTool caches, not dependencies

Caching a whole virtualenv can look faster because there is nothing to install. It is fragile: the venv holds absolute paths and links to the interpreter, so a Python patch release on the runner image can leave you with a broken environment. If you want that speed, use Poetry with in-project venvs keyed on both lockfile and Python version, or use uv.

Option 3: uv

uv is a drop-in faster installer from Astral. Its setup action manages the cache for you.

- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v10
  with:
    enable-cache: true
    cache-dependency-glob: |
      **/pyproject.toml
      **/uv.lock
- run: uv sync --frozen
- run: uv run pytest

Per the setup-uv README (checked October 2026), enable-cache defaults to auto, which turns caching on for GitHub-hosted runners and off for self-hosted ones. On self-hosted runners, including compiler.dev, set enable-cache: true explicitly. setup-uv does not install your project; you still run uv sync. For a pip-style workflow, uv pip install -r requirements.txt uses the same cache.

How to verify a cache hit

  1. The setup-python step logs Cache restored from key: setup-python-Linux-...-pip-.... A miss logs Cache not found.
  2. In pip install output, packages restored from cache show Using cached ... instead of Downloading ....
  3. At the end of a miss job, the post step prints Cache saved with key: ....
  4. gh cache list --key pip- --limit 20 (or --key setup-python) lists entries with size and last use.

Common mistakes

  1. No lockfile. A loose requirements.txt with unpinned versions means the key stays the same while resolved versions change. Pin with pip-compile, uv pip compile or a lockfile.
  2. Hashing a file that does not exist. With cache: pip and no requirements.txt or pyproject.toml, setup-python errors. Set cache-dependency-path.
  3. Sharing one key across Python versions. Include the version in explicit keys; setup-python already does.
  4. Caching site-packages directly. Same fragility as caching a venv.
  5. Only saving on PRs. Scope rules mean a PR cache is not visible to other PRs. See Caching in GitHub Actions.

Slow test suites are a separate issue from slow installs; see pytest slow tests and speeding up Django on GitHub Actions.

Size and limits

pip caches are usually 100 MB to 1 GB. Data-science stacks (torch, CUDA wheels) can be several GB each and will crowd out the 10 GB repository quota fast. See cache limits. If a single wheel is huge, consider whether the download is really your bottleneck.

FAQ

Should I cache site-packages or a virtualenv?

Generally no. Cache pip's download directory, or use uv. A venv cache is only safe if the key includes the exact Python version and lockfile hash.

How do I cache pip for multiple requirements files?

Pass them all to cache-dependency-path, one per line.

Is uv safe to adopt just for CI?

It reads standard requirements.txt and pyproject.toml. Try uv pip install -r requirements.txt first, which changes one line.

Why is my pip cache never hit on a new branch?

A new branch can read its own and the default branch's caches. If main never saves one, there is nothing to restore.

Next steps

Using Poetry? Read caching Poetry in GitHub Actions. For the wider picture, see how to speed up GitHub Actions. compiler.dev runs the same job on 8 vCPU machines for $0.012 per minute; see pricing.

Run this pytest suite on compiler.dev 200 free minutes

Start free