# Using uv in GitHub Actions for Faster Python CI

> Originally published on [DevToolHub](https://devtoolhub.com/uv-github-actions-python-ci/).

Switching a GitHub Actions workflow from pip to [uv](https://docs.astral.sh/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](https://github.com/astral-sh/setup-uv) 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:

```yaml
- 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](https://github.com/astral-sh/setup-uv/releases) before you copy this into a workflow.

## Installing Python: Two Valid Paths

**Let uv manage Python:**
```yaml
- 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:**
```yaml
- uses: actions/setup-python@v5
  with:
    python-version-file: ".python-version"
```

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

```yaml
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

```yaml
- 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](https://dev.to/hugovk/speed-up-ci-with-uv-53n1) (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
