The uv package manager has taken over my Python workflow this year, and I don’t miss pip at all. It’s a single binary written in Rust that replaces pip, pipx, virtualenv, pyenv, and most of poetry in one tool. Installs that used to take 30 seconds now finish in under two. If you manage Python projects on Linux servers, this is the tool that finally makes dependency management boring, in the best way.
The project comes from Astral, the same team behind the ruff linter. Development moves fast. The tool went from first release to the default choice in many CI pipelines in about two years.
Why the uv Package Manager Exists
Python packaging has been a mess for a long time. You needed pip for installs, virtualenv for isolation, pyenv for interpreter versions, and pipx for CLI tools. Four tools, four config styles, four sets of failure modes.
The uv package manager collapses all of that into one binary. It resolves dependencies with a proper SAT solver, caches aggressively, and hardlinks packages from a global cache instead of copying them into every virtual environment. That last trick is why a second install of the same package takes milliseconds.
Speed is the headline, but the lockfile is what sold me. uv.lock is cross-platform and deterministic. The same file resolves identically on your Debian server and your teammate’s Mac.
Installing the uv Package Manager
The install is a single script, no root needed:
curl -LsSf https://astral.sh/uv/install.sh | sh
It drops the binary into ~/.local/bin. If you already use pipx, pipx install uv works too. I covered pipx setup in my pipx tutorial if you need that first. On Arch it’s in the official repos as uv, and Homebrew has it for anyone on a Mac.
Check the version after install:
$ uv --version
uv 0.9.14
The 12 Commands Worth Learning
These twelve cover almost everything I do day to day:
uv init myproject: scaffold a new project with apyproject.tomluv add requests: add a dependency and update the lockfile in one stepuv add --dev pytest: add a dev-only dependencyuv remove requests: drop a dependency cleanlyuv sync: install exactly what the lockfile says, nothing moreuv run python script.py: run inside the project environment without activating anythinguv venv: create a bare virtual environment when you just want oneuv pip install flask: pip-compatible interface for old habits and old scriptsuv python install 3.13: download and manage Python interpretersuv tool install ruff: install CLI tools in isolation, the pipx replacementuv lock --upgrade: refresh the lockfile to the newest allowed versionsuv cache clean: clear the global cache when disk space gets tight
The one I use most is uv run. No more sourcing activate scripts. You point it at a script and the uv package manager makes sure the environment matches the lockfile before executing.
Useful uv Package Manager Environment Variables
A few settings change behavior globally. UV_CACHE_DIR moves the cache off a small root partition. UV_INDEX_URL points installs at an internal mirror, which matters behind a corporate firewall. UV_PYTHON_PREFERENCE=only-managed forces the managed interpreter builds and ignores whatever broken system Python apt left behind. I keep that last one set on every server.
Managing Python Versions with the uv Package Manager
This part replaced pyenv for me. The uv package manager downloads standalone CPython builds, so you never compile an interpreter again:
uv python install 3.12 3.13
uv python list
uv python pin 3.12
The pin command writes a .python-version file into the project. Anyone who runs uv sync in that directory gets the right interpreter automatically, downloaded on demand if it’s missing. On a fresh Debian box that means a working Python 3.13 project in under a minute, no apt packages, no deadsnakes PPA, no build dependencies.
Running Scripts with Inline Dependencies
My favorite feature hides in PEP 723 support. You can declare dependencies inside a single script file:
# /// script
# dependencies = ["httpx", "rich"]
# ///
import httpx
from rich import print
print(httpx.get("https://api.github.com").json())
Then run it with uv run script.py. It builds a throwaway environment, installs httpx and rich into it, and executes. First run takes a couple of seconds. Repeat runs hit the cache and start almost instantly. This killed my pile of half-broken utility virtualenvs overnight.
How Fast Is It Really
I benchmarked a cold install of a Django project with 42 dependencies on a small Debian VPS. pip took 28.4 seconds. uv with a warm cache took 0.9 seconds, and 6.2 seconds fully cold. That’s not a rounding error, it changes how CI pipelines feel. If you want to reproduce numbers like these yourself, hyperfine is the right tool for the job.
The hardlink trick also saves real disk space. Ten projects sharing numpy no longer means ten copies of numpy. My ~/.cache/uv sits around 1.2 GB while covering every project on the machine. Compare that with the old setup, where ncdu regularly found multi-gigabyte piles of duplicated site-packages directories.
When Not to Use the uv Package Manager
A few honest limits. Conda-based scientific stacks with non-Python binaries are still better served by conda or pixi. Some corporate proxies choke on its parallel downloads, though UV_CONCURRENT_DOWNLOADS=1 usually fixes that. And if your deployment tooling parses requirements.txt, you’ll want uv export --format requirements.txt in your release step.
Legacy projects with exotic setup.py files occasionally fail to build under uv’s stricter resolver. In two years I’ve hit that twice, and both times pinning an older setuptools fixed it.
Worth Switching?
Yes. I moved every personal project and two production servers to the uv package manager over one weekend, and the migration was mostly deleting tools. The official documentation is short and readable, and the GitHub repository has an active issue tracker if you hit something odd. Start with uv init on your next throwaway script and see how far it gets you. My guess is you won’t go back to pip either.