How to Switch to uv: Replace pip, virtualenv, and Poetry in Your Python Project
uv replaces pip, virtualenv, and Poetry with a single fast binary. Step-by-step guide to migrating your existing Python project and setting up GitHub Actions CI.
On this page
Python package management has always been too complicated. To do it properly you needed pip to install packages, virtualenv or venv to isolate environments, pyenv to manage Python versions, and pip-tools to generate a proper lockfile. That's four separate tools, four different config formats, and four things that can break in CI.
uv fixes all of that. It's a single Rust binary from Astral (the same team that built ruff) that handles everything. Not "a little faster" fast — we're talking 10 to 100 times faster than pip on cold installs, and near-instant on warm ones.
This guide is specifically for migrating an existing project. If you're starting fresh, the workflow is even simpler — but most of us have a requirements.txt or pyproject.toml that needs to come along for the ride.
What uv Replaces
| Old tool | uv equivalent |
|---|---|
| `pip install requests` | `uv add requests` |
| `python -m venv .venv` | `uv venv` |
| `pyenv install 3.12` | `uv python install 3.12` |
| `pip-compile requirements.in` | `uv lock` |
| `pip install -r requirements.txt` | `uv sync` |
- 1
Install uv
You don't need Python installed to install uv. It's a standalone binary.
bashcurl -LsSf https://astral.sh/uv/install.sh | shOn Windows (PowerShell):
powershellpowershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"Restart your terminal and run
uv --versionto confirm. - 2
Migrate from pip + requirements.txt
Navigate to your project root and initialize uv:
bashuv initThis creates a
pyproject.tomlif you don't already have one. Then import your existing requirements. If your requirements.txt is clean (no comments, no extras), you can do it in one line:bash — Quick importuv add $(cat requirements.txt | grep -v "^#" | xargs)Or use uv's pip-compatible interface for a file with pinned versions, comments, or extras:
bash — Safe alternativeuv pip install -r requirements.txtAfter that, run
uv lockto generate your lockfile. Commit bothpyproject.tomlanduv.lock. Going forward, useuv add package-nameinstead ofpip install, anduv run python script.pyinstead of activating the virtual environment manually. - 3
Migrate from Poetry
The easiest path is a tool called
migrate-to-uv, which runs viauvx(uv's tool runner, like npx for Python):bashuvx migrate-to-uvRun it in your project root. It reads your
pyproject.toml(Poetry format) and converts it to the uv format. Then rebuild:bashuv lock && uv syncA few things that don't auto-migrate cleanly:
- Optional dependency groups: Poetry uses
[tool.poetry.group.dev.dependencies]. uv uses[dependency-groups]with slightly different syntax. Check these manually after migration. - Private indexes: If your Poetry config references a private PyPI index, set it up in
[tool.uv.sources]in your newpyproject.toml. poetry runcalls: Replace allpoetry run Xwithuv run Xacross your scripts and documentation.
- Optional dependency groups: Poetry uses
- 4
Run Your Project
Once uv is managing your project, use
uv runfor everything. You don't need to activate the virtual environment manually:bash# Instead of: source .venv/bin/activate && python main.py uv run python main.py # Instead of: source .venv/bin/activate && pytest uv run pytest # Instead of: source .venv/bin/activate && flask run uv run flask run - 5
Pin Your Python Version
To pin a specific Python version for your project, create a
.python-versionfile in your project root. uv reads this file automatically:bashecho "3.12" > .python-versionYou can also install that Python version via uv if it's not already on your system:
bashuv python install 3.12 - 6
GitHub Actions CI
This is where uv's speed pays off most visibly. CI recreates your environment from scratch on every run — the difference between pip and uv is often 2 to 3 minutes vs 15 to 20 seconds.
yaml — .github/workflows/ci.ymlname: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v4 - name: Install Python run: uv python install - name: Install dependencies run: uv sync --frozen - name: Run tests run: uv run pytest tests/
Frequently Asked Questions
How much faster is uv than pip?
In benchmarks, uv installs packages 10 to 100 times faster than pip on cold cache and near-instant on warm cache. The difference is most noticeable in CI where the cache is cold on every run.
| Scenario | pip | uv |
|---|---|---|
| Install Django cold | ~45 seconds | ~3 seconds |
| Install Django warm | ~8 seconds | <1 second |
| Full data science stack cold | ~4 minutes | ~15 seconds |
Should I commit uv.lock to my repository?
Yes — always commit uv.lock. It records the exact resolved versions of every dependency (including transitive ones). Without it, two developers running uv sync at different times might install different patch versions of a dependency, leading to 'works on my machine' bugs.
Does uv support monorepos and workspaces?
Yes. uv supports workspaces similar to Cargo (Rust) and npm workspaces. Define workspace members in your root pyproject.toml:
[tool.uv.workspace]
members = ["packages/*"]Each member has its own pyproject.toml, but they share a single uv.lock at the workspace root. Dependencies are resolved together, preventing version conflicts across packages.
How do I add a dev-only dependency with uv?
Use the --dev flag:
uv add --dev pytest ruff mypyThis adds the package to the [dependency-groups] dev section of pyproject.toml rather than the main [project.dependencies]. Dev dependencies are installed with uv sync (default) but excluded when you do uv sync --no-dev for production deploys.
Can uv replace pipx for globally installed tools?
Yes. Use uv tool install to install CLI tools globally:
uv tool install ruff
uv tool install black
uv tool install httpieInstalled tools are isolated from each other and from your projects. Use uvx tool-name to run a tool once without installing it permanently — the equivalent of npx in the JavaScript ecosystem.