Python Packaging in 2025: uv, Poetry, and pip-tools Compared
Compare uv, Poetry, and pip-tools for Python dependency management. Speed benchmarks, lockfile strategies, virtual environments, monorepo support, and...
The Python Packaging Problem
Python's packaging ecosystem has been historically painful. pip does not have a lockfile. virtualenv requires manual management. Dependency resolution can be slow and nondeterministic. In 2025, we finally have good solutions.
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="200" rx="12" fill="#1a1a2e"/><rect x="0" y="0" width="600" height="28" rx="12" fill="#2d2d44"/><rect x="0" y="12" width="600" height="16" fill="#2d2d44"/><circle cx="18" cy="14" r="5" fill="#ef4444"/><circle cx="34" cy="14" r="5" fill="#f59e0b"/><circle cx="50" cy="14" r="5" fill="#2dd4bf"/><text x="300" y="18" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">docker-compose.yml</text><rect x="0" y="28" width="35" height="172" fill="#1e1e32"/><text x="25" y="48" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">1</text><text x="25" y="66" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">2</text><text x="25" y="84" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">3</text><text x="25" y="102" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">4</text><text x="25" y="120" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">5</text><text x="25" y="138" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">6</text><text x="25" y="156" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">7</text><text x="25" y="174" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">8</text><text x="25" y="192" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">9</text><text x="45" y="48" fill="#a855f7" font-size="11" font-family="monospace">version</text><text x="100" y="48" fill="#e2e8f0" font-size="11" font-family="monospace">: "3.8"</text><text x="45" y="66" fill="#a855f7" font-size="11" font-family="monospace">services</text><text x="105" y="66" fill="#e2e8f0" font-size="11" font-family="monospace">:</text><text x="55" y="84" fill="#3b82f6" font-size="11" font-family="monospace"> web</text><text x="80" y="84" fill="#e2e8f0" font-size="11" font-family="monospace">:</text><text x="55" y="102" fill="#2dd4bf" font-size="11" font-family="monospace"> image</text><text x="110" y="102" fill="#e2e8f0" font-size="11" font-family="monospace">: nginx:alpine</text><text x="55" y="120" fill="#2dd4bf" font-size="11" font-family="monospace"> ports</text><text x="102" y="120" fill="#e2e8f0" font-size="11" font-family="monospace">:</text><text x="55" y="138" fill="#e2e8f0" font-size="11" font-family="monospace"> - "80:80"</text><text x="55" y="156" fill="#2dd4bf" font-size="11" font-family="monospace"> volumes</text><text x="118" y="156" fill="#e2e8f0" font-size="11" font-family="monospace">:</text><text x="55" y="174" fill="#e2e8f0" font-size="11" font-family="monospace"> - ./html:/usr/share/nginx</text><rect x="365" y="164" width="2" height="14" fill="#6366f1" opacity="0.8"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">A well-structured configuration file is the foundation of reproducible infrastructure.</p></div>
uv: The Fast New Standard
uv by Astral (the creators of Ruff) is written in Rust and is dramatically faster than any other Python package manager. It is a drop-in replacement for pip, pip-tools, virtualenv, and more.
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create a new project
uv init my-project
cd my-project
# Project structure created:
# my-project/
# ├── pyproject.toml
# ├── .python-version
# └── src/
# └── my_project/
# └── __init__.py# pyproject.toml (uv-managed)
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115",
"uvicorn[standard]>=0.32",
"sqlalchemy>=2.0",
"pydantic>=2.9",
]
[project.optional-dependencies]
dev = [
"pytest>=8.3",
"ruff>=0.7",
"mypy>=1.13",
]
[tool.uv]
dev-dependencies = [
"pytest>=8.3",
"ruff>=0.7",
]# Add dependencies
uv add fastapi uvicorn sqlalchemy pydantic
uv add --dev pytest ruff mypy
# Lock dependencies (creates uv.lock)
uv lock
# Sync environment with lockfile
uv sync
# Run commands in the virtual environment
uv run python app.py
uv run pytest
uv run fastapi dev
# Install a specific Python version
uv python install 3.12
uv python pin 3.12
# Build and publish
uv build
uv publishuv speed comparison (installing 50 packages):
uv sync: 0.8 seconds
pip install: 45 seconds
poetry install: 38 seconds
pip-tools sync: 42 secondsuv is 50x faster than pip. This matters in CI/CD where every second counts.
Poetry: The Mature Solution
Poetry has been the standard for Python dependency management since 2018. It provides deterministic builds, a lockfile, virtual environment management, and publishing.
# Install Poetry
curl -sSL https://install.python-poetry.org | python3 -
# Create a new project
poetry new my-project
cd my-project
# Or initialize in existing directory
poetry init# pyproject.toml (Poetry)
[tool.poetry]
name = "my-project"
version = "0.1.0"
description = "My awesome project"
authors = ["Yash Pritwani <[email protected]>"]
[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.115"
uvicorn = {version = "^0.32", extras = ["standard"]}
sqlalchemy = "^2.0"
pydantic = "^2.9"
[tool.poetry.group.dev.dependencies]
pytest = "^8.3"
ruff = "^0.7"
mypy = "^1.13"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"# Add dependencies
poetry add fastapi uvicorn sqlalchemy
poetry add --group dev pytest ruff
# Install from lockfile
poetry install
# Run commands
poetry run python app.py
poetry run pytest
# Update dependencies
poetry update
poetry update fastapi # Update specific package
# Export to requirements.txt (for Docker)
poetry export -f requirements.txt --output requirements.txt --without-hashes
# Build and publish
poetry build
poetry publish<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 190" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="190" rx="12" fill="#0d1117"/><rect x="0" y="0" width="600" height="28" rx="12" fill="#1c2333"/><rect x="0" y="12" width="600" height="16" fill="#1c2333"/><circle cx="18" cy="14" r="5" fill="#ef4444"/><circle cx="34" cy="14" r="5" fill="#f59e0b"/><circle cx="50" cy="14" r="5" fill="#2dd4bf"/><text x="300" y="18" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="monospace">Terminal</text><text x="20" y="50" fill="#2dd4bf" font-size="11" font-family="monospace">$</text><text x="35" y="50" fill="#e2e8f0" font-size="11" font-family="monospace">docker compose up -d</text><text x="20" y="70" fill="#94a3b8" font-size="11" font-family="monospace">[+] Running 5/5</text><text x="20" y="88" fill="#2dd4bf" font-size="10" font-family="monospace"> ✓</text><text x="38" y="88" fill="#94a3b8" font-size="10" font-family="monospace">Network app_default Created</text><text x="20" y="106" fill="#2dd4bf" font-size="10" font-family="monospace"> ✓</text><text x="38" y="106" fill="#94a3b8" font-size="10" font-family="monospace">Container web Started</text><text x="20" y="124" fill="#2dd4bf" font-size="10" font-family="monospace"> ✓</text><text x="38" y="124" fill="#94a3b8" font-size="10" font-family="monospace">Container api Started</text><text x="20" y="142" fill="#2dd4bf" font-size="10" font-family="monospace"> ✓</text><text x="38" y="142" fill="#94a3b8" font-size="10" font-family="monospace">Container db Started</text><text x="20" y="165" fill="#2dd4bf" font-size="11" font-family="monospace">$</text><rect x="35" y="155" width="8" height="14" fill="#e2e8f0" opacity="0.7"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Docker Compose brings up your entire stack with a single command.</p></div>
pip-tools: The Simple Approach
pip-tools is the minimal solution: just pip-compile (generates lockfile) and pip-sync (installs from lockfile). No virtual environment management, no project scaffolding.
# Install pip-tools
pip install pip-tools
# Define requirements
# requirements.in
fastapi>=0.115
uvicorn[standard]>=0.32
sqlalchemy>=2.0
pydantic>=2.9
# requirements-dev.in
-r requirements.in
pytest>=8.3
ruff>=0.7
mypy>=1.13# Generate lockfile (requirements.txt)
pip-compile requirements.in --output-file requirements.txt
pip-compile requirements-dev.in --output-file requirements-dev.txt
# Install from lockfile
pip-sync requirements.txt
# For development
pip-sync requirements-dev.txt
# Update all
pip-compile --upgrade requirements.in
# Update specific package
pip-compile --upgrade-package fastapi requirements.inComparison
|---------|-----|--------|-----------|
Docker Integration
uv in Dockerfile (recommended):
FROM python:3.12-slim
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Set up project
WORKDIR /app
COPY pyproject.toml uv.lock ./
# Install dependencies (cached layer)
RUN uv sync --frozen --no-dev --no-install-project
# Copy source code
COPY src/ src/
# Install the project
RUN uv sync --frozen --no-dev
CMD ["uv", "run", "uvicorn", "app:main", "--host", "0.0.0.0", "--port", "8000"]Poetry in Dockerfile:
FROM python:3.12-slim
RUN pip install poetry
RUN poetry config virtualenvs.create false
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN poetry install --no-dev --no-interaction --no-ansi
COPY . .
CMD ["uvicorn", "app:main", "--host", "0.0.0.0", "--port", "8000"]pip-tools in Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app:main", "--host", "0.0.0.0", "--port", "8000"]<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="200" rx="12" fill="#1a1a2e"/><rect x="30" y="30" width="100" height="130" rx="6" fill="none" stroke="#3b82f6" stroke-width="1.5"/><text x="80" y="55" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="monospace">docker-</text><text x="80" y="70" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="monospace">compose</text><text x="80" y="85" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="monospace">.yml</text><line x1="45" y1="95" x2="115" y2="95" stroke="#3b82f6" stroke-width="0.5" opacity="0.5"/><rect x="50" y="105" width="50" height="8" rx="2" fill="#94a3b8" opacity="0.3"/><rect x="50" y="118" width="60" height="8" rx="2" fill="#94a3b8" opacity="0.3"/><rect x="50" y="131" width="40" height="8" rx="2" fill="#94a3b8" opacity="0.3"/><path d="M135,95 L175,95" stroke="#e2e8f0" stroke-width="2" marker-end="url(#arrow2)"/><defs><marker id="arrow2" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><rect x="180" y="20" width="130" height="35" rx="6" fill="#6366f1" opacity="0.85"/><text x="245" y="42" text-anchor="middle" fill="#ffffff" font-size="11" font-family="system-ui">Web App</text><rect x="180" y="62" width="130" height="35" rx="6" fill="#a855f7" opacity="0.85"/><text x="245" y="84" text-anchor="middle" fill="#ffffff" font-size="11" font-family="system-ui">API Server</text><rect x="180" y="104" width="130" height="35" rx="6" fill="#2dd4bf" opacity="0.85"/><text x="245" y="126" text-anchor="middle" fill="#1a1a2e" font-size="11" font-family="system-ui">Database</text><rect x="180" y="146" width="130" height="35" rx="6" fill="#f59e0b" opacity="0.85"/><text x="245" y="168" text-anchor="middle" fill="#1a1a2e" font-size="11" font-family="system-ui">Cache</text><rect x="370" y="40" width="200" height="130" rx="8" fill="none" stroke="#e2e8f0" stroke-width="1" stroke-dasharray="5,4"/><text x="470" y="62" text-anchor="middle" fill="#e2e8f0" font-size="10" font-family="system-ui">Docker Network</text><line x1="310" y1="37" x2="390" y2="80" stroke="#94a3b8" stroke-width="1" opacity="0.5"/><line x1="310" y1="79" x2="390" y2="100" stroke="#94a3b8" stroke-width="1" opacity="0.5"/><line x1="310" y1="121" x2="390" y2="120" stroke="#94a3b8" stroke-width="1" opacity="0.5"/><line x1="310" y1="163" x2="390" y2="140" stroke="#94a3b8" stroke-width="1" opacity="0.5"/><circle cx="400" cy="80" r="5" fill="#6366f1"/><circle cx="400" cy="100" r="5" fill="#a855f7"/><circle cx="400" cy="120" r="5" fill="#2dd4bf"/><circle cx="400" cy="140" r="5" fill="#f59e0b"/><text x="470" y="85" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">:3000</text><text x="470" y="105" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">:8080</text><text x="470" y="125" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">:5432</text><text x="470" y="145" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">:6379</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Docker Compose defines your entire application stack in a single YAML file.</p></div>
Our Recommendation
For new projects in 2025: Use uv. It is the fastest, has the best developer experience, manages Python versions, supports workspaces, and is PEP 621 compliant. The Astral team (behind Ruff) ships reliable software.
For existing Poetry projects: Stay with Poetry unless you hit performance issues in CI/CD. Migration to uv is straightforward if needed.
For minimal needs: pip-tools is fine if you only need a lockfile and already manage virtualenvs yourself.
At TechSaaS, we use uv for all new Python projects. The speed difference is transformative in CI/CD — our Docker builds with uv take 3 seconds for dependency installation versus 45 seconds with pip. We also use uv's Python version management to eliminate the need for pyenv on developer machines and CI runners.
Need help with tutorials?
TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.