}

uv: The Python Package Manager That Replaces pip, pyenv, and Poetry (2026)

uv: The Python Package Manager That Replaces pip, pyenv, and Poetry (2026)

The Python packaging ecosystem has long been fragmented. Installing a project typically meant juggling five or more tools: pyenv to manage Python versions, virtualenv to isolate environments, pip to install packages, pip-tools to compile lock files, and pipx to install CLI tools globally. Poetry or PDM for project management on top of that. Each tool has its own configuration, its own quirks, and its own learning curve.

uv collapses all of that into a single binary written in Rust. It was created by Astral — the same team behind ruff, the fastest Python linter — and it is genuinely 10 to 100 times faster than pip. Benchmarks show installing 23 packages takes pip 6.6 seconds and uv 0.15 seconds on a warm cache. On cold cache the difference is similar in scale.

This tutorial covers everything you need to switch your workflow to uv today.


What uv Replaces

Before uv, a typical Python developer's toolbox looked like this:

pip          — install packages
pyenv        — manage Python versions
virtualenv   — create isolated environments
pip-tools    — compile requirements.txt lock files
pipx         — install CLI tools in isolated environments
poetry/PDM   — project management and publishing
twine        — upload packages to PyPI

With uv, all of those collapse into one binary:

uv           — everything above, in one command

uv is not a wrapper around pip. It is a fully independent implementation of the Python package resolver and installer written in Rust. It speaks the same protocols (PyPI, PEP 517, PEP 660) so it is fully compatible with the existing ecosystem.


Installation

uv does not require Python to be installed first. Download a single static binary:

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

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

# Homebrew
brew install uv

# pip (if you already have Python)
pip install uv

After installation, restart your shell or source your profile. Verify:

uv --version
# uv 0.4.x

That is the entire installation. No dependencies, no Python required, no package managers to bootstrap.


Project Setup

Starting a New Project

uv init myproject
cd myproject

This creates a standard Python project layout:

myproject/
├── pyproject.toml
├── .python-version
├── README.md
└── src/
    └── myproject/
        └── __init__.py

Adding Dependencies

uv add requests fastapi
uv add --dev pytest ruff

uv resolves, downloads, and installs packages, then updates pyproject.toml and generates a uv.lock file automatically. No separate pip install step.

Running Code

uv run main.py
uv run python -c "import requests; print(requests.__version__)"
uv run pytest

uv run automatically activates the virtual environment for the duration of the command. You never need to run source .venv/bin/activate manually.


Python Version Management

uv manages Python installations directly, replacing pyenv entirely.

Install Python Versions

uv python install 3.12
uv python install 3.11 3.10   # install multiple at once
uv python install              # install the version in .python-version

Pin a Version for a Project

uv python pin 3.11
# writes 3.11 to .python-version

List Available and Installed Versions

uv python list
# cpython-3.13.0-linux-x86_64-gnu    <download available>
# cpython-3.12.7-linux-x86_64-gnu    /home/user/.local/share/uv/python/...
# cpython-3.11.10-linux-x86_64-gnu   /home/user/.local/share/uv/python/...

uv downloads official CPython builds from the Astral-maintained python-build-standalone project. These are statically linked, relocatable binaries that work on any compatible Linux distribution.


Virtual Environments

Creating Environments

# Create a venv in the current directory
uv venv

# Create with a specific Python version
uv venv --python 3.11

# Create at a custom path
uv venv /path/to/myenv

No Manual Activation Needed

The key change in workflow is that uv run handles activation automatically:

# Old workflow
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python main.py
deactivate

# New workflow
uv run main.py

If you do need to activate (for interactive shells, for example):

source .venv/bin/activate   # Linux/macOS
.venv\Scripts\activate      # Windows

Lock Files and Reproducible Installs

uv.lock

When you add a dependency, uv creates uv.lock. This is a cross-platform lock file that pins every package, including transitive dependencies, to exact versions and content hashes.

uv add django
# Resolves dependencies...
# Writes uv.lock

The lock file should be committed to version control. It ensures every developer and every CI run installs the exact same dependency tree.

Syncing from the Lock File

uv sync           # install all dependencies from uv.lock
uv sync --frozen  # fail if uv.lock would change (good for CI)

Updating Dependencies

uv lock --upgrade           # upgrade all packages to latest allowed versions
uv lock --upgrade-package requests  # upgrade only one package

pip Compatibility Mode

If you have an existing project using requirements.txt, uv provides a fully pip-compatible interface:

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

# Install a package
uv pip install django==5.0

# Compile requirements.in → requirements.txt (replaces pip-tools)
uv pip compile requirements.in -o requirements.txt

# Show installed packages
uv pip list

# Freeze current environment
uv pip freeze > requirements.txt

This means you can drop uv into any existing project and immediately benefit from the speed improvement without changing any other tooling.


Migrating from pip or Poetry in 5 Minutes

From a pip / requirements.txt Project

# Step 1: Create a uv project from your existing directory
uv init --no-workspace

# Step 2: Import your requirements
uv add $(cat requirements.txt | grep -v "^#" | tr '\n' ' ')
# Or keep using requirements.txt directly:
uv pip install -r requirements.txt

# Step 3: Generate a lock file
uv lock

# Step 4: Replace your workflow
# Before: pip install -r requirements.txt && python main.py
# After:  uv run main.py

From a Poetry Project

# Step 1: uv can read pyproject.toml directly
# Just remove the [tool.poetry] section and replace with standard [project]

# Step 2: Import Poetry dependencies to uv
uv add $(poetry show --no-dev --no-ansi | awk '{print $1}')

# Step 3: Add dev dependencies
uv add --dev $(poetry show --dev --no-ansi | awk '{print $1}')

# Step 4: Delete poetry.lock, add uv.lock to version control
rm poetry.lock
uv lock
git add uv.lock

The whole migration for a medium-sized project typically takes under five minutes.


PEP 723 Inline Script Dependencies

PEP 723 allows embedding dependency metadata directly in a Python script. uv supports this natively:

#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.11"
# dependencies = [
#   "requests",
#   "rich",
# ]
# ///

import requests
from rich import print

response = requests.get("https://api.github.com/repos/astral-sh/uv")
print(response.json()["description"])

Run it directly:

uv run script.py

uv reads the # /// script block, creates an isolated temporary environment, installs the listed dependencies, and executes the script. No pip install, no virtualenv, no setup. This makes Python scripts fully self-contained and shareable.


Global Tool Installation (Replacing pipx)

uv tool replaces pipx for installing CLI tools in isolated environments:

# Install a tool globally
uv tool install ruff
uv tool install httpie
uv tool install black

# Run a tool without installing it
uv tool run ruff check .
# or the short form:
uvx ruff check .

# List installed tools
uv tool list

# Upgrade a tool
uv tool upgrade ruff

# Uninstall
uv tool uninstall black

Tools installed with uv tool get their own isolated environment but are available on your PATH. uvx is a shortcut for uv tool run — great for one-off commands without a permanent install.


Publishing Packages (Replacing twine)

# Build the package
uv build
# Creates dist/mypackage-0.1.0.tar.gz and dist/mypackage-0.1.0-py3-none-any.whl

# Publish to PyPI
uv publish

# Publish to a custom index
uv publish --index https://my-private-pypi.example.com/

# Publish with explicit credentials
uv publish --token $PYPI_TOKEN

uv publish replaces both python -m build and twine upload in a single command.


CI/CD with uv

GitHub Actions

Here is a complete workflow showing how to use uv in CI. The speed improvement on install steps is typically 60–80% compared to pip.

name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v3
        with:
          version: "latest"
          enable-cache: true

      - name: Set up Python
        run: uv python install

      - name: Install dependencies
        run: uv sync --frozen

      - name: Run tests
        run: uv run pytest

      - name: Lint
        run: uv run ruff check .

The astral-sh/setup-uv action caches the uv binary and the package cache across runs, making subsequent CI runs even faster. The --frozen flag on uv sync ensures the CI fails if anyone forgot to update the lock file.

GitLab CI

image: python:3.12-slim

variables:
  UV_CACHE_DIR: "$CI_PROJECT_DIR/.uv-cache"

cache:
  paths:
    - .uv-cache/

before_script:
  - pip install uv
  - uv sync --frozen

test:
  script:
    - uv run pytest

lint:
  script:
    - uv run ruff check .

Workspace Support for Monorepos

If your repository contains multiple Python packages, uv supports workspaces:

# Root pyproject.toml
[tool.uv.workspace]
members = ["packages/*"]
mymonorepo/
├── pyproject.toml          # workspace root
├── uv.lock                 # single lock file for all packages
└── packages/
    ├── api/
    │   └── pyproject.toml
    ├── core/
    │   └── pyproject.toml
    └── cli/
        └── pyproject.toml

All packages in the workspace share a single lock file and can declare dependencies on each other:

# packages/api/pyproject.toml
[project]
name = "api"
dependencies = [
    "core",       # local workspace package
    "fastapi",
]
uv sync                        # sync all packages
uv run --package api main.py   # run in a specific package context

Key Commands Reference

Task uv command
New project uv init myproject
Add dependency uv add requests
Add dev dependency uv add --dev pytest
Remove dependency uv remove requests
Install all deps uv sync
Run script uv run main.py
Install Python uv python install 3.12
Pin Python version uv python pin 3.11
Install global tool uv tool install ruff
Run one-off tool uvx ruff check .
Build package uv build
Publish package uv publish
pip compatibility uv pip install -r requirements.txt

Summary

uv is the most significant shift in Python tooling since virtualenv was introduced. It is faster than any alternative by a large margin, it consolidates a fragmented toolchain into a single binary, and it is backward-compatible with the existing pip and PyPI ecosystem.

For new projects, start with uv init and never look back. For existing projects, the migration path is straightforward: add uv.lock to version control, replace pip install with uv sync, and replace python with uv run in your scripts and CI. The performance improvement on CI alone typically justifies the five-minute migration cost.

Install it, try uv run on your next script, and see for yourself.

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

Leonardo Lazzaro

Software engineer and technical writer. 10+ years experience in DevOps, Python, and Linux systems.

More articles by Leonardo Lazzaro