Meet uv: one tool for Python packages, versions and projects
One Rust binary that covers the jobs of pip, pyenv, virtualenv, pipx and Poetry, and that you can adopt one command at a time.
You know the feeling. You run pip install -r requirements.txt and wait. You check your phone, make a coffee, come back, and find a dependency conflict waiting for you.
This is not a post against pip. pip has served the Python community well for many years, and it is still the standard installer. The problem is the toolbox that has grown around it: pip to install, virtualenv to isolate, pyenv for Python versions, pipx for command-line tools, Poetry for project management. That is five tools with five command-line interfaces to run one project.
uv asks a simple question: what if one tool did all of that, and did it quickly? Astral reports that uv is 10 to 100 times faster than pip. Let's look at what it does.
What uv is
uv is a Python package and project manager built by Astral, the team behind the Ruff linter. It is written in Rust, ships as a single binary, and the standalone installer does not need Python on the machine.
Here is what it replaces:
| What you used before | What uv offers instead |
|---|---|
| pip | uv pip |
| virtualenv, venv | uv venv |
| pip-tools | uv pip compile, uv pip sync |
| pyenv | uv python |
| pipx | uv tool, uvx |
| Poetry, PDM | uv init, uv add, uv lock, uv sync |
You do not have to adopt all of it at once. Start with the pip interface and move further when you are ready.
Installing uv
# macOS and Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass `
-c "irm https://astral.sh/uv/install.ps1 | iex"
# If you already have Python, from PyPI
pip install uv
That is all. There is one binary and nothing else to configure.
Start with a faster pip
If you want no change to your workflow, this is the place to start:
uv venv
uv pip install requests
uv pip install -r requirements.txt
uv pip uninstall requests
uv pip freeze
The commands look familiar on purpose. Astral describes the pip interface as a drop-in replacement for common pip and pip-tools workflows. It is not an exact clone, and the compatibility page lists the differences. The one you will meet first: uv installs into a virtual environment by default. It uses the active environment, or a .venv in the current or a parent directory. That is why the example starts with uv venv. To install into the system Python instead, pass --system.
If you want to go all in, you can alias it:
alias pip="uv pip"
With that one line, you are using uv.
A lockfile that covers every platform
This is where uv moves past plain pip. When you use uv to manage a project, which the next section covers, it writes a uv.lock file. The lockfile records your whole dependency tree: not only the packages you declared, but everything they depend on, pinned to exact versions. The uv documentation calls it a universal, or cross-platform, lockfile: one file covers different operating systems, CPU architectures and Python versions. You commit it to version control.
In practice this means:
- A teammate clones the repository, runs
uv sync, and gets the same versions you have. - CI installs the same versions on every run.
- "It works on my machine" becomes a much rarer conversation.
Project management
Let's create a project from scratch.
uv init my-cool-app
cd my-cool-app
uv init creates a pyproject.toml, a .python-version file, a README and some starter code, and initialises a Git repository. It does not create the virtual environment or the lockfile yet. uv creates .venv and uv.lock the first time you run a project command such as uv add, uv run, uv sync or uv lock.
To add dependencies:
uv add fastapi uvicorn # runtime dependencies
uv add --dev pytest httpx # the "dev" dependency group
uv add --group lint ruff # a custom dependency group
--dev is shorthand for --group dev. Dependency groups are stored in the standard [dependency-groups] table of pyproject.toml and are not included in the metadata of a package you publish.
To run your code:
uv run uvicorn main:app --reload
uv run pytest
Before every uv run, uv checks that the lockfile matches pyproject.toml and that the environment matches the lockfile, and fixes either if it does not. You no longer need to remember to activate a virtual environment.
Onboarding a teammate looks like this:
git clone <your-repo-url>
cd your-repo
uv sync # the whole setup
uv run python main.py
One command takes you from a fresh clone to a working environment that matches the lockfile.
Managing Python versions
uv also installs and manages Python itself, which covers most of what I used pyenv for:
uv python install 3.12
uv python install 3.11 3.12 3.13 # several at once
uv python list # installed and available versions
uv python list --only-installed # installed versions only
uv python pin 3.12 # write .python-version
If a project needs a Python version you do not have, uv does not stop to ask: by default it downloads the version and carries on. If you would rather control that, set python-downloads to manual, and uv then downloads Python only when you run uv python install. Removing that kind of friction adds up over a career.
Self-contained scripts
This one surprised me the first time I saw it. PEP 723 lets a script declare its own dependencies in a comment block at the top of the file:
# /// script
# requires-python = ">=3.11"
# dependencies = ["requests", "rich"]
# ///
import requests
from rich import print
data = requests.get("https://api.github.com").json()
print(data)
Run it with:
uv run my_script.py
uv reads the inline metadata, finds or downloads a matching Python, builds an isolated environment with the listed dependencies and runs the script in it. There is no requirements file and no virtual environment to set up: you share one file, and anyone with uv can run it. It suits data scripts, automation tasks and small utilities.
You do not have to write the block by hand. uv init --script my_script.py creates a script with an empty block, and uv add --script my_script.py requests rich adds dependencies to it.
Command-line tools, the pipx way
# Install a tool and put its commands on your PATH
uv tool install ruff
uv tool install black
# Run a tool once without installing it
uvx ruff check .
uvx black --check .
# Run a specific version
uvx ruff@0.6.0 check .
uvx is an alias for uv tool run, and it runs the tool in a temporary, isolated environment. It is one of the commands I use most. If you need a formatter once, there is no reason to install it into your project: run it with uvx.
How fast is fast
These are Astral's own figures, so treat them as a starting point and measure on your own projects:
- The uv README describes uv as 10 to 100 times faster than pip, with the benchmark method published in the repository.
- In the launch post of February 2024, Astral reported uv as 8 to 10 times faster than pip and pip-tools without a cache, and 80 to 115 times faster with a warm cache.
- The same post reported virtual environment creation as about 80 times faster than
python -m venvand 7 times faster than virtualenv.
The warm cache is where it shows most. uv keeps a global cache, so once a package has been downloaded and built, other projects reuse it instead of fetching it again. Astral says uv uses copy-on-write and hardlinks on file systems that support them, which keeps the disk cost of many environments low. For that to work, the cache has to be on the same file system as your environments; otherwise uv falls back to copying.
How it compares with other tools
pip. For the same operations, uv pip is faster, and the project commands add a lockfile on top. pip remains the standard installer and is the right choice where you cannot add another tool, but for daily development uv pip is an easy upgrade.
Poetry. Poetry is mature and well liked, and uv's project workflow will feel familiar to its users. Since Poetry 2.0, released in January 2025, both tools read the standard [project] table in pyproject.toml. The practical differences are speed, and that uv also installs Python versions and runs command-line tools, so one binary covers more of the workflow.
PDM. PDM also uses the standard project metadata and supports dependency groups. uv covers more ground, with Python version management and the pipx-style tool commands. PDM is a good tool and still maintained.
Conda. Conda solves a different problem: it manages packages that are not Python at all, such as compiled system libraries and CUDA toolkits. If your project uses only packages from PyPI, uv is the better fit. If you depend on Conda's binary packages, keep using Conda or its faster drop-in replacement, Mamba.
pyenv. The uv python commands cover the everyday pyenv tasks: install a version, list versions, pin a project. For most projects you can stop using pyenv.
Docker and CI
# Dockerfile
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.10.5 /uv /uvx /bin/
WORKDIR /app
# Install dependencies first, so this layer is cached
COPY pyproject.toml uv.lock ./
RUN uv sync --locked --no-dev --no-install-project
# Then add the code and install the project itself
COPY . .
RUN uv sync --locked --no-dev
The uv documentation recommends pinning a specific uv version in the image tag rather than using latest. Splitting the sync in two keeps the dependency layer cached while your code changes: --no-install-project installs the dependencies but not the project itself.
# GitHub Actions
- uses: astral-sh/setup-uv@v7
- run: uv sync --locked
- run: uv run pytest
Two flags matter here. --locked asserts that uv.lock is up to date with pyproject.toml and fails if it is not, which is what you want in CI: a stale lockfile becomes a red build. --frozen installs from uv.lock as it is, without checking it against pyproject.toml. It is useful when pyproject.toml is not available yet, as in some Docker layers, but it will not catch a lockfile someone forgot to update.
Migrating
From pip and requirements.txt
uv init
uv add -r requirements.txt
uv add reads the requirements file, adds each entry to pyproject.toml and writes uv.lock, so there is no separate lock step. If you use pip-tools, with direct dependencies in requirements.in and pinned versions in requirements.txt, keep your current versions by passing the pinned file as constraints:
uv add -r requirements.in -c requirements.txt
From Poetry
- Most of
pyproject.tomlcarries over. - Move dependencies from
[tool.poetry.dependencies]to thedependencieslist in the[project]table, and development dependencies to[dependency-groups]. - Rewrite Poetry's own constraint syntax as standard specifiers on the way:
^2.31becomes>=2.31,<3. - Run
uv lockto createuv.lock, then removepoetry.lock.
The lowest-risk path
alias pip="uv pip"
Keep your virtual environments and requirements files and change nothing else. Remember that uv pip expects a virtual environment, so add --system anywhere you used to install into the system Python.
Is it ready for production?
Yes. On the day this post was first published, the current release was uv 0.10.5, in the 0.10 series that began on 5 February 2026. The version number is below 1.0, but Astral describes uv as stable software that is widely used in production, and it publishes a versioning policy: breaking changes go only into minor releases, and the uv.lock format is part of the public API. Editor support is there too: PyCharm added uv support in version 2024.3.2.
Where to go next
- The uv documentation is clear and well organised.
- The GitHub repository: the release notes double as a detailed changelog.
- Managing Python Projects With uv: An All-in-One Solution on Real Python is a thorough tutorial.
- uv: Unified Python packaging on Astral's blog explains the design behind the project commands.
Final thought
uv will not make you a better programmer. What it does is remove friction: the small, slow, repetitive steps that add up to real frustration over a long project. You get faster installs, reproducible environments, one tool instead of five, and onboarding that takes one command.
pip laid the foundation. uv builds on what the ecosystem has learned since.
Give it ten minutes. Install it, run uvx ruff check . on your codebase and watch the result come back. Then try uv sync on your next project. I would be surprised if you went back.
Sources
- Astral, uv documentation: working on projects, project structure and the lockfile, Python versions, running scripts, using tools
- Astral, uv documentation: using Python environments, compatibility with pip, migrating from pip to a uv project, caching
- Astral, uv documentation: using uv in Docker, using uv in GitHub Actions, command reference, versioning policy
- Astral, uv on GitHub, including BENCHMARKS.md and the release history; setup-uv releases
- Astral blog, uv: Python packaging in Rust (15 February 2024) and uv: Unified Python packaging (20 August 2024)
- Python Enhancement Proposals, PEP 723: Inline script metadata
- Poetry, Announcing Poetry 2.0.0 (5 January 2025); JetBrains, PyCharm 2024.3.2; Mamba documentation