Python has an amazing ecosystem, but its package management has long been its achilles's heel. If you've ever faced dependency hell, slow installs, or complicated virtual environments, you're not alone.
Enter uv
, a blazing fast Python package manager by Astral, the creators of ruff
and other developer-first tools. Built in Rust, uv
aims to be the definitive way to handle Python environments β fast, reliable, and easy.
In this post, weβll explore:
- What
uv
is and how to use it - How it compares to
pip
andpoetry
- How it eliminates dependency issues
- Using
uv
withdocker
andFastAPI
for production-ready performance
π§ What is uv
?
uv
is an ultrafast Python package manager and environment manager that can:
- Create and manage virtual environments
- Install and resolve dependencies (like
pip
orpoetry
) - Automatically cache packages
- Lock dependencies for reproducible builds
- Replace
pip
,virtualenv
, and even parts ofpoetry
Built in rust
, it prioritizes speed and reliability, much like tools such as ripgrep
, fd
, or bat
.
β‘ Getting Started with uv
Installation of uv
is quick and platform-specific:
Linux/macOS (Unix-like):
curl -LsSf https://astral.sh/uv/install.sh | sh
macOS (with Homebrew):
brew install uv
==> Downloading https://formulae.brew.sh/api/formula.jws.json
==> Downloading https://formulae.brew.sh/api/cask.jws.json
To reinstall 0.7.21, run:
brew reinstall uv
Windows (PowerShell):
powershell -c "irm https://astral.sh/uv/install.ps1 | more"
Verify the installation:
uv --version
uv 0.7.21
β‘ Install and Manage Python Versions
One of uvβs most powerful and underrated features is its built-in Python version manager β no need for tools like pyenv
or conda
.
To see all available versions that you can install, run:
uv python list
cpython-3.14.0b4-macos-aarch64-none <download available>
cpython-3.14.0b4+freethreaded-macos-aarch64-none <download available>
cpython-3.13.5-macos-aarch64-none <download available>
cpython-3.13.5+freethreaded-macos-aarch64-none <download available>
cpython-3.12.11-macos-aarch64-none .local/share/uv/python/cpython-3.12.11-macos/bin/python3.12
cpython-3.11.13-macos-aarch64-none <download available>
cpython-3.10.18-macos-aarch64-none <download available>
cpython-3.9.23-macos-aarch64-none <download available>
cpython-3.9.6-macos-aarch64-none /usr/bin/python3
cpython-3.8.20-macos-aarch64-none <download available>
pypy-3.11.13-macos-aarch64-none <download available>
pypy-3.10.16-macos-aarch64-none <download available>
pypy-3.9.19-macos-aarch64-none <download available>
pypy-3.8.16-macos-aarch64-none <download available>
graalpy-3.11.0-macos-aarch64-none <download available>
graalpy-3.10.0-macos-aarch64-none <download available>
graalpy-3.8.5-macos-aarch64-none <download available>
This shows a list of installable Python versions (e.g., 3.8, 3.9, 3.10, 3.11, etc.)
To Install a specific Python version
uv python install 3.11
uv
downloads the interpreter, caches it, and makes it available for your environments β no system-level changes required.
This downloads and installs the specified version of Python locally for use with uv environments.
You can also set a default version for the current project (or globally if desired):
uv python pin 3.11
This creates a .python-version
file in your directory, which uv
automatically detects when creating environments or running Python commands.
π Common uv
Commands
1. Create a new virtual environment
uv venv
Using CPython 3.11
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
It automatically detects your Python version or uses .python-version
.
2. Creating a Python project
Now that uv is installed, kick off a fresh Python project with a single command:
(venv) uv init .
This scaffolds your project directory with a clean, minimal setup:
βββ .gitignore # Files and directories to ignore in Git
βββ .python-version # Python version management file
βββ main.py # Hello world Python script
βββ .venv # .venv directory
βββ pyproject.toml # Dependency and project configuration file
βββ README.md # Project documentation
3. Install packages
uv add "fastapi[all]" requests pandas python-dotenv
Letβs see the file pyproject.toml
:
[project]
name = "uv-py-3-11"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastapi[all]>=0.116.1",
"pandas>=2.3.1",
"python-dotenv>=1.1.1",
"requests>=2.32.4",
]
4. Organizing Dependencies with Groups in uv
One of the standout features of uv is its support for dependency groups. This allows you to separate dependencies based on their purpose β making your project leaner in production and more organized during development.
This is especially useful when:
You want to minimize production dependencies for security and performance.
You need to cleanly separate tools like testing libraries, linters, and documentation tools.
Add Development Dependencies
- To add pytest, pytest-cov etc as a development-only dependency, simply run:
uv add --dev pytest pytest-cov pre-commit
Add Custom Groups
- Youβre not limited to just dev β you can name groups however you like. For example, to add ruff as a linter under a custom lint group:
uv add --group lint ruff
- After adding dependencies into groups, your pyproject.toml will look something like this:
[project]
name = "uv-py-3-11"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.1"
dependencies = [
"fastapi[all]>=0.116.1",
"pandas>=2.3.1",
"python-dotenv>=1.1.1",
"requests>=2.32.4",
]
[dependency-groups]
dev = [
"pre-commit>=4.2.0",
"pytest>=8.4.1",
"pytest-cov>=6.2.1",
]
lint = [
"ruff>=0.12.3",
]
- Here,
pytest
andruff
are added under separate dependency groups. -
uv
ensures all dependency groups are compatible and resolves them together when creating the lockfile. - We can run the command to install dependencies from
uv.lock
file:
uv sync
- By default, this command installs all dependency groups. To exclude specific groups, use the
--no-group
flag:
uv sync --no-group dev --no-group lint
5. Removing Dependencies with uv
- Just as easily as you can add dependencies with
uv
, you can remove them too β keeping your project clean and lightweight. - Letβs say you no longer need
fastapi
in your project. To remove it, simply run:
uv remove fastapi
- This command will:
- Uninstall the package from your environment.
- Automatically update your
pyproject.toml
file to reflect the change. - Clean up any unused packages if they're no longer required by anything else.
6. After Removal: What Changes?
- Your
pyproject.toml
file will no longer listfastapi
under the[project]
dependencies section. - uv takes care of maintaining consistency between your environment and configuration β no manual edits needed.
π₯ uv
vs pip
: Escaping Pythonβs Dependency Hell π
if youβve spent enough time in the Python ecosystem, youβve likely run into the dreaded dependency hell β conflicting packages, silent failures, or those classic it works on my machine moments.
Letβs break down why this happens with traditional tools like pip
, and how uv
fixes it for good.
π΅βπ« The Problem with pip
While pip is the default package manager and gets the job done in many simple scenarios, its dependency resolution process isβ¦ well, letβs call it optimistic at best.
Example problem with pip:
pip install package-a package-b
# package-a needs urllib3==1.26
# package-b needs urllib3==2.0
What happens?
pip
installs them anyway β potentially choosing one version without warning. You wonβt see an error during installation, but you'll almost certainly get runtime errors or subtle bugs later. This is how you end up in dependency hell.
β
How uv
Fixes This
uv
approaches dependency management with a modern, deterministic mindset. It doesn't just download and install packages β it resolves your full dependency graph and ensures there are no version conflicts before proceeding.
Here's what makes uv better:
- π Strict conflict resolution β if two packages are incompatible, uv will fail fast rather than install something broken.
- π¦ Deterministic installs β every environment gets the same dependency versions thanks to a generated lockfile.
- β‘οΈ Blazing fast β built in Rust, uv is significantly faster than pip and even poetry.
Result: No surprises in CI, no it works on my machine issues.
Normally uv
should be used as a replacement for pip
. But if you want you can generate requirements.txt
file from uv.lock
:
uv export --format requirements-txt > requirements.txt
π uv
vs poetry
Feature | Poetry | uv |
---|---|---|
Primary Role | Allβinβone project manager | Package installer & resolver |
Language | Python | Rust |
Performance | π’ Slower | β‘ Extremely fast |
Virtual Env Mgmt | β
Yes, fully integrated (poetry env β¦ ) |
β
Yes, fully integrated (uv venv ) |
pip Compatibility | β No. Uses its own commands (e.g. poetry add ) |
β
Yes. A dropβin replacement (uv pip install β¦ ) |
Lockfile | Creates and manages its own poetry.lock
|
Reads requirements.lock , pdm.lock , and poetry.lock ; does not create its own unique lockfile |
Docker Friendliness | β οΈ Complex. Requires installing Poetry itself, often leading to larger images or multiβstage builds | β Excellent. Distributed as a single binary, making for minimal, fastβbuilding Docker images |
Why uv
is better:
- Simpler to use (no poetry.lock weirdness)
- Drop-in support with
requirements.txt
andpyproject.toml
- Way faster (2β10x in benchmarks)
- Easier to integrate with Docker/CI/CD
- Transparent: it doesn't hijack your workflow
π³ Building a FastAPI App with uv
and Docker
Letβs build a clean, production-ready FastAPI + Docker + uv setup.
Directory Structure
uv-py-3-11/
βββ .gitignore
βββ .dockerignore
βββ .python-version
βββ README.md
βββ main.py
βββ pyproject.toml
βββ uv.lock
βββ Dockerfile
main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello from uv + FastAPI!"}
pyproject.toml
[project]
name = "uv-py-3-11"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastapi[all]>=0.116.1",
"pandas>=2.3.1",
"python-dotenv>=1.1.1",
"requests>=2.32.4",
]
Dockerfile
# Use the official lightweight Python 3.12 image as the base
FROM python:3.12-slim-bullseye
# Copy the `uv` binary from the official uv image hosted on GitHub Container Registry
# This allows us to use `uv` for managing dependencies without installing it manually
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
# Set the working directory inside the container to /app
# All subsequent commands will be run from this directory
WORKDIR /app
# Copy all local files (project source code) into the container's /app directory
COPY . .
# Install project dependencies as defined in `pyproject.toml` and `uv.lock`
# `--frozen` ensures the lockfile matches, and `--no-cache` skips caching for a smaller image
RUN uv sync --frozen --no-cache
# Expose port 8000 to the host so that the FastAPI app can be accessed externally
EXPOSE 8000
# Start the FastAPI application using the installed virtual environment
# Note: Make sure `.venv/bin/fastapi` exists (i.e., fastapi CLI installed in the venv)
CMD ["/app/.venv/bin/fastapi", "run", "main.py", "--port", "8000"]
Build and Run
docker build -t my-fastapi-app .
docker run -p 8000:8000 my-fastapi-app
β
Fast cold starts
β
Dependency-locked
β
Zero pip conflicts
β
Lean Docker image
π§ͺ Benchmarks
A quick test on a moderately sized pyproject.toml
:
Action | pip |
poetry |
uv |
---|---|---|---|
install (10 deps) |
~50s | ~7s | ~1s |
lock |
N/A | ~3s | ~1s |
run script |
manual | auto | uv run |
π Final Thoughts
If you're looking for a package manager that:
- Is faster than pip or poetry
- Handles dependency resolution like a boss
- Plays nicely with
pyproject.toml
- Works out of the box in Docker/CI
- Feels like the modern Python tooling we deserve
Then uv
is absolutely worth switching to.
Youβll spend less time fighting your environment and more time building things.
π Resources
- Official Docs: https://docs.astral.sh/uv/
- GitHub: https://github.com/astral-sh/uv
- Astral (Ruff creators): https://astral.sh
Top comments (0)