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.
| Cache | Worth it | Why |
|---|---|---|
| pip cache dir | Yes | Skips downloads and local wheel builds |
A virtualenv (.venv) | Rarely | Tied to one Python build and path; breaks on minor upgrades |
~/.cache/pre-commit | Yes, separately | Hook environments, keyed on .pre-commit-config.yaml |
.pytest_cache, .mypy_cache, .ruff_cache | Sometimes | Tool 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
- The setup-python step logs
Cache restored from key: setup-python-Linux-...-pip-.... A miss logsCache not found. - In
pip installoutput, packages restored from cache showUsing cached ...instead ofDownloading .... - At the end of a miss job, the post step prints
Cache saved with key: .... gh cache list --key pip- --limit 20(or--key setup-python) lists entries with size and last use.
Common mistakes
- No lockfile. A loose
requirements.txtwith unpinned versions means the key stays the same while resolved versions change. Pin withpip-compile,uv pip compileor a lockfile. - Hashing a file that does not exist. With
cache: pipand norequirements.txtorpyproject.toml, setup-python errors. Setcache-dependency-path. - Sharing one key across Python versions. Include the version in explicit keys; setup-python already does.
- Caching
site-packagesdirectly. Same fragility as caching a venv. - 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.
Related
- Cache Cargo and Rust Builds in GitHub ActionsCache the Cargo registry, git dependencies and target directory in GitHub Actions with Swatinem/rust-cache or actions/cache, and avoid bloated caches.
- Cache Go Modules and Build Cache in GitHub ActionsCache Go modules (GOMODCACHE) and the build cache (GOCACHE) in GitHub Actions with setup-go or actions/cache. Keys, go.sum, and verifying hits.
- Cache Gradle Builds in GitHub ActionsCache Gradle dependencies and build cache in GitHub Actions with gradle/actions/setup-gradle or actions/cache. Keys, read-only PRs, cleanup and checks.
- Cache npm Dependencies in GitHub ActionsCache npm dependencies in GitHub Actions with setup-node or actions/cache. Working YAML, the right key, why not node_modules, and how to confirm a hit.