Skip to content

uv: A Complete Guide to Python's Fastest Package Manager

uv

uv replaces a half-dozen traditional Python tools with a single binary: interpreter installation, virtual environments, dependency management, version locking, CLI tools, and code formatting. In benchmarks, it creates virtual environments 50x faster than python -m venv and installs packages 5-40x faster than pip depending on cache state.

Note

Already familiar with uv and looking for a quick command reference? See the uv reference page.

What uv does

Built by Astral (the team behind Ruff), uv ships as a standalone binary with no Python dependency, so you can bootstrap an entire Python development environment from scratch. One install, one CLI, one mental model.

uv achieves large speedups over pip through aggressive caching, parallel downloads, and an optimized dependency resolver written in Rust. The benchmarks below show the gap: installing 23 packages from a warm cache takes pip 6.6 seconds and uv 0.15 seconds.

uv aligns with modern Python packaging standards. It uses pyproject.toml for project metadata following PEP 621, generates cross-platform lockfiles, and works with standard build backends. Projects created with uv remain compatible with pip and other Python tools.

Before uv, the same set of capabilities required combining pyenv for interpreter management, virtualenv for environment isolation, pip-tools for dependency locking, and pipx for CLI tools. Poetry unified some of these but historically used non-standard metadata (Poetry 2.0, released January 2025, added PEP 621 support) and has no built-in Python version management.

For a deeper look at why this fragmentation happened and how uv fits in, see Why are there so many Python packaging tools? and Why you should try uv if you use Python.

How fast is uv?

The speed claims are easy to verify. Here are timed comparisons of uv 0.8 against pip 26.0 on the same machine (Apple Silicon, Python 3.14).

Virtual environment creation

python -m venv takes about 2 seconds. uv venv finishes in 35 milliseconds.

$ time python -m venv .venv
python3 -m venv .venv  1.02s user 0.18s system  1.951 total

$ time uv venv .venv
Using CPython 3.14.3
Creating virtual environment at: .venv
uv venv .venv  0.00s user 0.01s system  0.035 total

Installing packages (cold cache)

With no cached wheels, installing five libraries (boto3, requests, pandas, fastapi, sqlalchemy) and their 23 total dependencies:

$ time pip install boto3 requests pandas fastapi sqlalchemy
...
Successfully installed 23 packages
pip install  5.65s user 1.19s system  13.137 total

$ time uv pip install boto3 requests pandas fastapi sqlalchemy
Resolved 23 packages in 294ms
Prepared 23 packages in 481ms
Installed 23 packages in 58ms
uv pip install  0.30s user 0.63s system  0.871 total

pip: 13.1 seconds. uv: 0.87 seconds. 15x faster, and the gap widens with more packages.

Reinstalling packages (warm cache)

The warm-cache scenario reflects CI pipelines where layers are cached or local development where you recreate an environment:

$ time pip install boto3 requests pandas fastapi sqlalchemy
...
Successfully installed 23 packages
pip install  4.91s user 0.96s system  6.597 total

$ time uv pip install boto3 requests pandas fastapi sqlalchemy
Resolved 23 packages in 16ms
Installed 23 packages in 91ms
uv pip install  0.03s user 0.11s system  0.150 total

pip: 6.6 seconds. uv: 0.15 seconds. 44x faster. uv’s cache stores pre-built wheels and uses hard links to avoid copying files into the virtual environment, so “installing” cached packages is nearly instantaneous.

Summary

Operation pip uv Speedup
Create virtual environment 1.95s 0.03s 56x
Install 23 packages (cold cache) 13.1s 0.87s 15x
Install 23 packages (warm cache) 6.6s 0.15s 44x

These numbers come from a single machine. Results will vary by hardware, network speed, and package size. The pattern holds across environments: uv’s parallel downloads, compiled resolver, and hard-link caching produce consistent order-of-magnitude speedups for common Python workflows.

Installation

uv ships as a standalone binary with no Python dependency. For full instructions, see How to install uv. The recommended approach is the official installer:

# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows (PowerShell)
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

Alternative methods:

# Homebrew (macOS)
brew install uv

# pip (if Python is already installed)
pip install uv

After installing, confirm it works:

uv --version

Platform-specific details are covered in the dedicated how-to guides for macOS, Linux, and Windows. To keep uv current, see How to upgrade uv.

Core workflows

Managing Python versions

uv can install and manage multiple Python interpreters without pyenv or system package managers.

# Install a specific Python version
uv python install 3.12

# List available versions
uv python list

# Install multiple versions for testing
uv python install 3.11 3.12 3.13

Tip

uv run and uv sync automatically download the Python version specified in your pyproject.toml if it isn’t already installed. Every developer gets the same interpreter version without manual setup.

Unlike pyenv, which compiles Python from source (a process that can take minutes and requires build dependencies), uv downloads prebuilt binaries. uv python install finishes in seconds. uv stores these interpreters in a shared cache, so multiple projects using the same Python version share a single installation.

You can pin a project to a specific Python version with a .python-version file:

uv python pin 3.12

This writes a .python-version file that uv (and other tools) respect. When a collaborator clones the project and runs uv sync, uv reads this file and installs the correct interpreter automatically.

To make a uv-installed Python available outside of uv projects (for example, so python3 resolves to it in your shell), see How to add Python to your system path with uv.

For a side-by-side comparison with pyenv, read How do pyenv and uv compare for Python interpreter management?. If you are currently using pyenv, the guide How to switch from pyenv to uv walks through the migration step by step.

Creating and managing projects

uv init scaffolds a new project with a pyproject.toml. The virtual environment and lockfile are created when you first run uv sync or uv add.

# Create a new project
uv init my-project
cd my-project

# Add dependencies
uv add requests
uv add pandas numpy

# Add development dependencies
uv add --dev pytest ruff

# Install everything (creates/updates .venv and uv.lock)
uv sync

# Run a command inside the project environment
uv run python main.py

Note

uv.lock pins every direct and transitive dependency to an exact version, ensuring reproducible installs across machines. Unlike pip freeze output, uv’s lockfile is cross-platform by default.

uv supports two project types out of the box: applications and libraries. An application (the default) suits web services, scripts, and data pipelines. A library is intended for packages you publish to PyPI. The difference affects how uv generates your pyproject.toml and how it handles version constraints. Read Understanding uv init project types for guidance on which to choose.

# Create a library project
uv init --lib my-package

# Create an application (default)
uv init my-app

uv also handles optional dependencies and dependency groups, letting you define sets of extras like [project.optional-dependencies] for features or [dependency-groups] for development tools. For the mechanics of installing groups with uv sync --group, see Understanding dependency groups in uv.

For a hands-on walkthrough of creating a project from scratch, follow the tutorial Create your first Python project. For libraries with native extensions, see Build a Python library with a C extension or Build a Python library with a Rust extension.

Changing your project’s Python version is straightforward:

uv python pin 3.13
uv sync

See How to change the Python version of a uv project for details.

Lockfiles are central to reproducible environments. uv.lock pins every transitive dependency to an exact version and records platform-specific resolution so that uv sync produces identical environments on macOS, Linux, and Windows.

For more on how lockfiles work and why they matter, see What is a lock file? and How to use a uv lockfile for reproducible Python environments.

Running scripts

uv run executes a Python script after ensuring the project’s virtual environment is up to date. For one-off scripts that aren’t part of a project, you can specify dependencies inline using PEP 723 metadata:

# /// script
# dependencies = ["requests", "rich"]
# requires-python = ">=3.11"
# ///

import requests
from rich import print

response = requests.get("https://api.github.com/zen")
print(response.text)

Run it with:

uv run script.py

uv reads the inline metadata, creates a temporary environment with the declared dependencies, and executes the script. No project setup, no manual virtualenv. The environment is cached, so running the same script a second time skips dependency installation entirely.

You can also pass dependencies on the command line without modifying the script:

uv run --with requests script.py

This is useful for quick experiments or when running someone else’s script with an additional package. You can even specify a Python version:

uv run --python 3.11 script.py

For interactive work, uv launches a REPL with your project’s dependencies available:

uv run python

See How to run a Python REPL with uv, How to run the IPython shell in your uv project, and How to run a Jupyter Notebook with uv. For test suites, How to run tests using uv covers pytest configuration and common workflows.

Managing CLI tools

Many Python packages ship command-line tools: Ruff, Black, Jupyter, and dozens more. uv provides two ways to run them:

uvx runs a tool in a temporary, isolated environment. No installation required. The tool is cached for fast subsequent runs:

uvx ruff check .
uvx black --check .

uv tool install installs a tool permanently so it’s available as a regular command:

uv tool install ruff
ruff check .  # now available directly

This replaces pipx for most use cases. Each tool gets its own isolated environment, so tools never conflict with each other or with your project’s dependencies.

You can also upgrade installed tools:

uv tool upgrade ruff
uv tool upgrade --all

The distinction between uvx and uv tool install maps to how you use the tool. If you run it occasionally or want to try it once, use uvx. If you use it daily and want it on your PATH, use uv tool install. Both approaches isolate the tool’s dependencies from your project.

For a detailed comparison of when to use uv run versus uvx, see When to use uv run vs uvx.

Formatting code

uv includes a built-in uv format command that formats Python code using Ruff’s formatter. This follows the same pattern as cargo fmt in Rust: the formatter is a separate tool under the hood, but uv provides a convenient interface so you don’t need to think about it as a separate dependency.

# Format all Python files in the project
uv format

# Check formatting without making changes
uv format --check

# Show a diff of what would change
uv format --diff

Formatting settings are read from [tool.ruff.format] in your pyproject.toml. If you already use Ruff for formatting, uv format uses the same configuration.

pip compatibility

If you have existing workflows built around pip and requirements files, uv provides a drop-in compatible interface:

# Install from requirements.txt
uv pip install -r requirements.txt

# Install a package
uv pip install requests

# Generate a locked requirements file from loose constraints
uv pip compile requirements.in -o requirements.txt

# Sync an environment to match a requirements file exactly
uv pip sync requirements.txt

Tip

These commands produce identical results to their pip equivalents but run significantly faster. Swap pip for uv pip in your CI scripts for an immediate speed boost without changing your project structure.

uv pip compile takes a loose requirements.in file (or pyproject.toml) and produces a fully resolved requirements.txt with pinned versions and hashes. The --universal flag generates a single requirements file that works across platforms, a feature pip-tools does not offer.

For a full comparison of pip and uv, see What’s the difference between pip and uv?. To migrate an existing requirements.txt-based project to uv’s native project format, follow How to migrate from requirements.txt to pyproject.toml with uv.

You can also use pip inside a uv-managed virtual environment if needed. See How to use pip in a uv virtual environment.

How uv compares to alternatives

Feature uv pip Poetry pyenv pipx
Install packages Yes Yes Yes No No
Lockfile Yes No Yes No No
Virtual environments Yes No Yes No Yes (per-tool)
Python version management Yes No No Yes No
Run CLI tools Yes No No No Yes
Script execution Yes No No No No
Performance 15-50x faster Baseline Pure Python Compiles from source Pure Python
Standards compliance pyproject.toml (PEP 621) requirements.txt pyproject.toml (PEP 621 in 2.0+) N/A N/A

For in-depth comparisons:

When to use uv

New projects: uv is the strongest default for new Python projects today. A single uv init gives you a standards-compliant pyproject.toml, a lockfile, and a virtual environment. No other tool setup required.

Existing pip/requirements.txt projects: Start by replacing pip install with uv pip install for an immediate speed boost. When ready, migrate to pyproject.toml with uv init and uv add. The guide How to migrate from requirements.txt to pyproject.toml with uv covers this process.

Existing Poetry projects: uv supports the same pyproject.toml-based workflow with better performance and standards compliance. See How to migrate from Poetry to uv.

CI/CD pipelines: uv’s speed advantage is most dramatic in CI, where environments are recreated on every run. Its caching and fast resolution cut minutes from pipeline execution. See Setting up GitHub Actions with uv and How to use uv in a Dockerfile.

One-off scripts and automation: Inline script dependencies (PEP 723) make uv a strong choice for standalone scripts. You can share a single .py file with its dependencies declared inside, and anyone with uv installed can run it without further setup. This is useful for data processing scripts, automation tasks, and quick prototypes.

Important

If your project depends on non-Python libraries (C/Fortran extensions for scientific computing, CUDA binaries), conda manages those cross-language dependencies in ways uv cannot. See Why should I choose conda? for guidance. For installing GPU-accelerated stacks like RAPIDS with uv directly, see How to install RAPIDS with uv.

Some organizations with established Poetry workflows may prefer to stay with Poetry if the migration cost outweighs the benefits; see How do uv and Poetry compare? for a balanced assessment.

Editor and tool integration

uv creates ordinary virtual environments in .venv, following the same structure as venv and virtualenv. Most editors detect this directory automatically and configure the Python interpreter path. Some need manual configuration, particularly when the .venv directory is in a non-standard location or when using workspace setups.

For project-scaffolding instructions you can paste into a CLAUDE.md or session prompt, see the Modern Python Project Setup Guide for AI Assistants.

For task running, see How to use Poe the Poet as a task runner with uv.

Testing with uv

uv pairs well with pytest for running tests:

uv add --dev pytest
uv run pytest

For testing across multiple Python versions:

uv python install 3.11 3.12 3.13
uv run --python 3.11 pytest
uv run --python 3.12 pytest
uv run --python 3.13 pytest

Guides:

Advanced topics

Virtual environment customization: By default, uv creates a .venv directory in your project root. Some workflows require placing the environment elsewhere, for example when working with Docker volumes or shared filesystems where the default location causes performance issues. See How to customize uv’s virtual environment location.

Docker: uv’s standalone binary and fast installs make it well-suited for container builds. Because uv has no runtime dependency on Python, you can copy the binary into a Docker image and use it immediately. Combined with its caching behavior and fast resolution, this can cut Docker build times from minutes to seconds when dependencies haven’t changed. See How to use uv in a Dockerfile.

Dynamic versioning: For packages that derive their version from git tags or other sources, see How to add dynamic versioning to uv projects.

Build backend: uv init now defaults to uv_build, uv’s own build backend (stable since July 2025). Earlier versions defaulted to Hatchling. Either way, uv build produces standard wheels and sdists that work with pip and any PEP 517-compatible tool. For background on the original Hatchling default, see Why did uv originally use Hatch as a build backend?.

Publishing packages: Once you’ve built a package, uv can publish it to PyPI:

uv build
uv publish

For a walkthrough of the full publishing process, see Publishing your first Python package to PyPI.

Troubleshooting

Most uv errors have clear messages that point to the fix. Two common issues new users encounter:

Warning

The “No project Table Found” error occurs when you run uv add or uv sync in a directory without a pyproject.toml, or with a pyproject.toml that lacks a [project] section. See How to fix “No project Table Found” error in uv.

Warning

Python version incompatibility errors happen when your pyproject.toml specifies a requires-python range that conflicts with the installed interpreter or with a dependency’s requirements. See How to fix Python version incompatibility errors in uv.

Learn more

Tutorials

How-to guides

Explanations

Reference

Last updated on

Please submit corrections and feedback...