DEV Community

Cover image for πŸš€ Why uv is the best thing that happened to my python workflows (Yes, it beats pip & poetry)
MANOVISHNU ADEPU
MANOVISHNU ADEPU

Posted on

πŸš€ Why uv is the best thing that happened to my python workflows (Yes, it beats pip & poetry)

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 and poetry
  • How it eliminates dependency issues
  • Using uv with docker and FastAPI 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 or poetry)
  • Automatically cache packages
  • Lock dependencies for reproducible builds
  • Replace pip, virtualenv, and even parts of poetry

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Windows (PowerShell):

powershell -c "irm https://astral.sh/uv/install.ps1 | more"
Enter fullscreen mode Exit fullscreen mode

Verify the installation:

uv --version
uv 0.7.21
Enter fullscreen mode Exit fullscreen mode

⚑ 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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

3. Install packages

uv add "fastapi[all]" requests pandas python-dotenv
Enter fullscreen mode Exit fullscreen mode

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",
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
  • 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",
]
Enter fullscreen mode Exit fullscreen mode
  • Here, pytest and ruff 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
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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 list fastapi 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

πŸ†š 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 and pyproject.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
Enter fullscreen mode Exit fullscreen mode

main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello from uv + FastAPI!"}
Enter fullscreen mode Exit fullscreen mode

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",
]
Enter fullscreen mode Exit fullscreen mode

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"]

Enter fullscreen mode Exit fullscreen mode

Build and Run

docker build -t my-fastapi-app .
docker run -p 8000:8000 my-fastapi-app
Enter fullscreen mode Exit fullscreen mode

βœ… 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


Top comments (0)