Programming 08 Październik 2026

UV — a fast Python package and environment manager (tutorial)

Also in:
PL EN

uv is a Python manager written in Rust by Astral (the creators of ruff). In a single tool it replaces what used to require several: pip, venv/virtualenv, pip-tools, pyenv, pipx, and to a large extent poetry as well. Its hallmark is speed — installs and dependency resolution are typically an order of magnitude faster than with pip, thanks to aggressive caching and parallelism.

This article is a practical tutorial: what to install, how to set up an environment for a repository, what uv offers, and what to watch out for.


Why use it

  • One tool instead of five — Python, virtual environments, packages, CLI tools, lockfile.
  • Speed — a global, shared cache and hard-linking of files make repeat installs nearly instantaneous.
  • It also installs the Python interpreter itself — you don't need a "system" Python of a given version; uv downloads a standalone build (e.g. 3.12) without touching the system.
  • Ecosystem compatibility — it can act like pip (the uv pip interface) and read requirements.txt and pyproject.toml.

Installation

Official installer (Linux/macOS):

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

Alternatives:

# via pipx
pipx install uv

# macOS (Homebrew)
brew install uv

Verification and update:

uv --version
uv self update

uv installs into the user's directory (e.g. ~/.local/bin), so it does not require administrator privileges.


Managing Python versions

uv can download and switch interpreters independently of the system — this is a big advantage.

uv python list                 # available and installed versions
uv python install 3.12         # download a standalone CPython 3.12
uv python install 3.11 3.13    # several at once
uv python pin 3.12             # write .python-version for this project

uv python pin creates a .python-version file, which uv honors when creating environments — a good way to make the whole repo use a consistent version.

Real-world example: the system only had Python 3.14, while the cloned repo required 3.12. Running uv python install 3.12 was enough, and the environment was created on the right version — without installing anything "on the system".


Two ways of working

uv supports two styles worth distinguishing:

  1. "pip-compatible" mode (uv venv + uv pip …) — you stay close to classic pip/venv, just faster. Ideal for existing repositories and a quick start.
  2. Project mode (uv init/uv add/uv sync) — uv manages pyproject.toml and the uv.lock file, giving full, reproducible project handling (the equivalent of poetry).

Both are covered below.


Quick start: an environment for a cloned repo (pip mode)

The most common scenario — you have a cloned repo and want to run it locally.

cd my-repo

# 1. Create a virtualenv (defaults to .venv) on a chosen Python version.
uv venv --python 3.12

# 2. Install dependencies. Pick whichever matches the repo:
uv pip install -r requirements.txt          # classic requirements file
uv pip install -e .                          # editable install from pyproject/setup
uv pip install numpy pandas "astropy~=7.1"   # explicit packages

# 3. Run things (either activate, or call binaries directly)
source .venv/bin/activate                    # optional
uv run python -m pytest                       # runs inside the project env

A few useful uv pip install variants:

# Install a wheel from a custom index (e.g. a project-specific wheel host)
uv pip install --find-links https://example.org/wheels "somepkg==1.2.3"

# Use an alternative package index
uv pip install --index-url https://my.mirror/simple/ requests

Real-world example: in a project using wxPython (which has no regular wheels on PyPI for Linux), uv pip install --find-links <wheel-index> "wxPython==4.2.3" worked together with the remaining dependencies — one command, no compiling from source.

By default the environment is created in .venv inside the project directory — convenient for editors (see the VS Code section).


Project mode: uv init, add, sync, run

When you start a new project or want full reproducibility:

uv init my-project        # scaffold: pyproject.toml, .python-version, README, src/
cd my-project

uv add requests           # add a dependency (updates pyproject.toml + uv.lock)
uv add --dev pytest ruff  # dev-only dependencies
uv remove requests        # remove a dependency

uv sync                   # make the env match uv.lock exactly
uv lock                   # regenerate the lockfile from pyproject.toml

uv run pytest             # run a command in the project env (auto-syncs first)
uv run python app.py

The key files:

  • pyproject.toml — declared dependencies and project metadata.
  • uv.lock — exactly pinned versions (commit it; it guarantees an identical environment for everyone).
  • .python-version — the pinned interpreter version.

uv run is crucial here: before running a command, uv makes sure in the background that the environment is in sync with the lock — you don't need to activate the venv manually or remember to run uv sync.


Working with existing projects (pip / requirements / poetry)

  • requirements.txt — uv pip install -r requirements.txt. To generate and "pin" versions, use uv pip compile:

bash uv pip compile requirements.in -o requirements.txt # resolve and pin uv pip sync requirements.txt # make env match exactly

  • pyproject.toml (PEP 621) — uv reads it natively: uv sync or uv pip install -e ..
  • Poetry project — if pyproject.toml uses the [tool.poetry] section, you can still use pip mode (uv venv + uv pip install <dependencies>), and eventually consider migrating the metadata to the standard [project] table, which uv fully supports.

Global tools: uv tool and uvx (a pipx replacement)

For installing CLI tools in isolation (like pipx):

uv tool install ruff        # install a CLI tool globally, isolated
uv tool list                # list installed tools
uv tool upgrade --all

uvx ruff check .            # run a tool ephemerally, without installing it
uvx black@25.1.0 .          # pin a specific version for a one-off run

uvx is a shortcut for "download and run on the fly" — great for one-off tasks without cluttering the system.


Lockfile and reproducibility

In project mode, uv.lock ensures that everyone (and CI) gets identical versions.

uv lock                 # create/refresh the lockfile
uv sync                 # install exactly what the lock says
uv sync --frozen        # fail if the lock is out of date (good for CI)
uv sync --no-dev        # production install without dev dependencies

Rule of thumb: commit uv.lock for applications. For libraries, usually not (a library shouldn't impose exact versions on its users).


VS Code / editor integration

  • Create the environment as .venv in the project directory (the default for uv venv) — editors detect it automatically.
  • In VS Code, select the interpreter: Ctrl+Shift+P → "Python: Select Interpreter" → ./.venv/bin/python.
  • It's worth pointing tasks and launch configurations directly at .venv/bin/… (.venv/bin/python, .venv/bin/ruff), so they work regardless of whether the terminal has an active venv.
  • If terminal auto-activation is unreliable, you can force it with a custom terminal profile (bash --init-file) that sources .venv/bin/activate itself.

Command cheat sheet

Command What it does
uv python install 3.12 download a Python interpreter
uv venv --python 3.12 create .venv on a given version
uv pip install <pkg> install a package (pip interface)
uv pip install -r requirements.txt install from a requirements file
uv pip compile req.in -o req.txt resolve and "pin" versions
uv init start a new project
uv add / remove <pkg> add/remove a dependency (project)
uv sync match the environment to uv.lock
uv lock generate/refresh the lockfile
uv run <cmd> run a command in the project environment
uv tool install <cli> install a CLI tool globally
uvx <cli> run a tool one-off
uv cache clean clear the cache
uv self update update uv

Pitfalls and best practices

  • uv pip install is not pip "on the system" — it installs into the active (or specified) environment, not globally. That's a good thing, but it can be confusing for people used to pip install without a venv.
  • Specify the environment explicitly when you have no active venv: uv pip install --python .venv/bin/python ….
  • Commit uv.lock (applications) and .python-version — they are the foundation of reproducibility.
  • The cache is global — repeat installs are fast; when you run low on disk space, use uv cache clean.
  • Hard-linking works best when the cache and the project are on the same filesystem; otherwise uv warns and copies (you can force this with --link-mode=copy).
  • Don't needlessly mix uv pip (pip mode) with uv add/uv sync (project mode) in one project — pick one way of working.

Summary

uv reduces working with Python to a few fast commands: it downloads the interpreter, creates the environment, installs dependencies and runs code, and in project mode provides a reproducible uv.lock. To start, uv venv + uv pip install is enough; when you need full control and repeatability — move to uv init/uv add/uv sync/uv run. In practice it replaces pip, venv, pyenv and pipx, and often poetry too — with noticeably shorter waiting times.


References