Cache Poetry Dependencies in GitHub Actions
Last updated
To cache Poetry in GitHub Actions, install Poetry before actions/setup-python, then set cache: poetry. setup-python then caches Poetry's virtualenv, keyed on poetry.lock. The step order is the part people get wrong, and it is the first thing to check if the cache step errors.
Option 1: setup-python with cache: poetry
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Poetry
run: pipx install poetry
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: poetry
- run: poetry install --no-interaction
- run: poetry run pytest
Per the setup-python README (checked October 2026), cache: poetry caches the virtualenv directories, one for each Poetry project it finds, and the default key hashes poetry.lock. The action runs Poetry to find those directories, so Poetry has to be installed first. pipx is preinstalled on GitHub-hosted Ubuntu images. On other runners, install pipx first or use pip install poetry.
Use cache-dependency-path when the lockfile is in a subdirectory:
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: poetry
cache-dependency-path: services/api/poetry.lock
Option 2: explicit actions/cache with an in-project venv
Poetry puts virtualenvs in a shared directory by default (~/.cache/pypoetry/virtualenvs on Linux) with a hashed name. Making the venv live in the project, as .venv, gives you a fixed path to cache.
- uses: actions/checkout@v6
- run: pipx install poetry
- uses: actions/setup-python@v7
id: py
with:
python-version: "3.13"
- run: poetry config virtualenvs.in-project true
- uses: actions/cache@v6
with:
path: .venv
key: venv-${{ runner.os }}-py${{ steps.py.outputs.python-version }}-${{ hashFiles('poetry.lock') }}
restore-keys: |
venv-${{ runner.os }}-py${{ steps.py.outputs.python-version }}-
- run: poetry install --no-interaction
Three details matter here:
- The key has the exact Python version.
steps.py.outputs.python-versiongives the full version, such as3.13.1. A virtualenv contains links to the interpreter it was created with. If the runner image updates Python, a venv restored under an old key will be broken. Putting the exact version in the key forces a rebuild. poetry installstill runs. On a cache hit it finds everything installed and finishes in seconds. On a partial restore viarestore-keys, it adds or updates only what changed.- Set
virtualenvs.in-projectbefore the cache step. Otherwise Poetry creates the venv somewhere else and.venvis empty.
Option 3: cache the download cache only
If you prefer not to cache a venv, cache Poetry's download cache and let poetry install rebuild the venv each time:
- uses: actions/cache@v6
with:
path: ~/.cache/pypoetry
key: poetry-${{ runner.os }}-${{ hashFiles('poetry.lock') }}
restore-keys: |
poetry-${{ runner.os }}-
This is safer but slower, because wheels are still unpacked on every run. Use poetry config cache-dir to confirm the real path on your runner.
What to cache and what not to
| Item | Cache? | Notes |
|---|---|---|
In-project .venv | Yes, with Python version in key | Fastest restore |
~/.cache/pypoetry | Yes | Safe, saves downloads only |
| The Poetry install itself | Optional | pipx install poetry takes seconds |
dist/ build output | No | Rebuilt per release |
Pin the Poetry version (pipx install poetry==2.x) if you cache a venv. A Poetry upgrade can change how the environment is laid out, and an unpinned install makes your key incomplete.
How to verify a cache hit
- Look for
Cache restored from key: ...in the setup-python or cache step. On a miss you seeCache not found for input keys: .... - In
poetry installoutput, a warm run printsNo dependencies to install or update. A cold one lists each package withInstalling .... - With explicit
actions/cache, branch on the output while debugging:if: steps.cache.outputs.cache-hit != 'true'runs only on a miss. gh cache list --key venv- --limit 20shows whether entries are being saved.
Common mistakes
- Installing Poetry after setup-python. With
cache: poetrythe action fails to find Poetry. - Not committing
poetry.lock. The key is then built from nothing useful. - Missing Python version in the venv key. Leads to confusing import errors after an image update.
- Using
poetry installwithout--no-interaction. Rarely a problem in CI, but cheap insurance. - Treating the cache as the source of truth. If something odd happens, delete the entry (
gh cache delete <id>) and rebuild.
For how scope and eviction affect all of these, see Caching in GitHub Actions and cache limits.
FAQ
Is it better to cache .venv or the Poetry cache directory?
.venv is faster; the cache directory is safer. Use .venv with a key that has the exact Python version and poetry.lock hash.
Does cache: poetry work without pipx?
It needs poetry on the PATH. Any install method works as long as it runs before setup-python.
Should I use uv instead of Poetry in CI?
That is a project decision. If you only want faster installs, see the uv notes in caching pip.
How do I cache Poetry in a monorepo?
Give each project its own cache-dependency-path and a distinct key prefix, so changes to one lockfile do not invalidate the others.
Next steps
For pytest runtime, see pytest slow tests. To see what else to fix, read how to speed up GitHub Actions. compiler.dev gives you 200 free trial minutes to compare your job on a faster machine; 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.