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 beforeWhat uv offers instead
pipuv pip
virtualenv, venvuv venv
pip-toolsuv pip compile, uv pip sync
pyenvuv python
pipxuv tool, uvx
Poetry, PDMuv 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:

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

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

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