Skip to main content

Using uv in GitHub Actions for Faster Python CI

Updated 4 min readMarkdown

Originally published on DevToolHub.

Switching a GitHub Actions workflow from pip to uv means swapping pip install -r requirements.txt for uv sync --locked, adding one official action, and turning on a cache flag. The payoff isn't the "10-100x faster" number uv's own docs advertise for cold installs — in a real CI job with a warm cache, the honest number is closer to 1.4x. Still worth it, but worth knowing which number is real before you promise your team a 10x pipeline.

What setup-uv Actually Installs

The official astral-sh/setup-uv action downloads the uv binary, puts it on PATH, and — if you ask it to — manages a cache. It does not manage your Python interpreter by default; that's a separate step. Pin it to a commit SHA, not a floating version tag:

- name: Install uv
  uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
  with:
    enable-cache: true

As of this writing v10.3.0 (released 2026-10-09) is the latest tag. Check the releases page before you copy this into a workflow.

Installing Python: Two Valid Paths

Let uv manage Python:

- name: Set up Python
  run: uv python install

This respects a .python-version file or a requires-python pin in pyproject.toml.

Use the official setup-python action instead:

- uses: actions/setup-python@v5
  with:
    python-version-file: ".python-version"

For matrix builds across Python versions, pass the version through setup-uv directly:

strategy:
  matrix:
    python-version: ["3.11", "3.12", "3.13"]
steps:
  - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
    with:
      python-version: ${{ matrix.python-version }}

Caching: What You Get for Free, What You Still Configure

enable-cache: true on setup-uv persists uv's package cache across runs using GitHub's own Actions Cache, keyed against your lockfile. This replaces the cache: pip line you'd have had on actions/setup-python before.

⚠️ Important: on self-hosted runners, the cache directory grows unbounded across runs. Point UV_CACHE_DIR inside your workspace and run uv cache prune --ci as a cleanup step.

Running the Actual Job

- name: Install the project
  run: uv sync --locked --all-extras --dev
- name: Run tests
  run: uv run pytest tests

uv sync --locked fails the build instead of silently re-resolving if uv.lock is out of sync — that's the flag you want in CI.

The Real Numbers, Not the Marketing Ones

A real migration writeup from the CPython blurb tool (PR python/blurb#32):

Job No cache With cache
Linting (pre-commit) 60s → 37s (1.62x) 11s → 11s (no change)
Testing (tox) 2m 0s → 1m 26s (1.40x) 1m 58s → 1m 22s (1.44x)

That's the number to plan around — 1.4-1.6x on a real job, not the 10-100x cold-install figure from uv's own benchmark page.

What Breaks When You Migrate from tox + pip

The real gotcha: uv environments don't have pip installed by default. Any tox.ini line that shells out to pip install -r requirements.txt breaks. Fix it with uv_seed = True or rewrite to tox's native deps = declaration understood by tox-uv.

Frequently Asked Questions

Q: Does uv replace actions/setup-python entirely? A: Not necessarily — both paths work, and some teams keep setup-python for its own caching behavior.

Q: Why pin setup-uv to a commit SHA instead of a version tag? A: A tag can move later, a SHA can't — standard practice for third-party actions.

Q: Does uv sync --locked fail the build if the lockfile is stale? A: Yes, which is exactly why you use it in CI instead of plain uv sync.

Q: Is the 10-100x speed claim fake? A: No — it's a real cold-install number. Warm-cache CI jobs see closer to 1.4-1.6x.

Quick Summary:

  • astral-sh/setup-uv, SHA-pinned, with enable-cache: true covers most projects
  • uv sync --locked fails loudly on a stale lockfile — use it in CI
  • Real CI speedup: 1.4-1.6x, not the 10-100x marketing number
  • uv environments don't ship pip — fix tox.ini with uv_seed = True or deps =
  • Self-hosted runners need manual cache pruning

No comments yet. Be the first to comment.