Python Poetry: Modern Dependency Management Guide

Poetry replaces the fragmented Python packaging ecosystem — pip, virtualenv, setup.py, requirements.txt — with a single tool that manages dependencies, virtual environments, and package publishing. A single pyproject.toml declares everything; poetry.lock guarantees reproducible installs across all environments. This guide covers the full Poetry workflow from project creation to production Docker builds.

Installation

# Install Poetry (official installer — do NOT use pip)
curl -sSL https://install.python-poetry.org | python3 -

# Windows (PowerShell)
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -

# Verify
poetry --version  # Poetry 1.8.x

# Configure Poetry to create virtualenvs inside the project (useful for Docker + IDEs)
poetry config virtualenvs.in-project true

# Configure to use system Python by default
poetry config virtualenvs.prefer-active-python true

Creating a New Project

# New project (creates directory and boilerplate)
poetry new my-api
cd my-api

# Initialize Poetry in an existing directory
cd existing-project
poetry init  # interactive prompts

# Project structure after `poetry new`:
# my-api/
#   pyproject.toml
#   README.md
#   my_api/
#     __init__.py
#   tests/
#     __init__.py

# Install all dependencies (creates virtualenv if needed)
poetry install

# Run a command in the virtualenv
poetry run python my_api/main.py
poetry run pytest

# Activate the virtualenv shell
poetry shell

pyproject.toml Deep Dive

[tool.poetry]
name = "my-api"
version = "1.0.0"
description = "FastAPI microservice"
authors = ["Avinash "]
readme = "README.md"
license = "MIT"
packages = [{include = "my_api"}]

[tool.poetry.dependencies]
python = "^3.11"
fastapi = "^0.111.0"
uvicorn = {extras = ["standard"], version = "^0.30.0"}
pydantic = "^2.7.0"
sqlalchemy = {extras = ["asyncio"], version = "^2.0.0"}
asyncpg = "^0.29.0"
python-jwt = "^4.0.0"
redis = {extras = ["hiredis"], version = "^5.0.0"}

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
pytest-asyncio = "^0.23.0"
httpx = "^0.27.0"
ruff = "^0.4.0"
mypy = "^1.10.0"

[tool.poetry.group.test.dependencies]
pytest-cov = "^5.0.0"
factory-boy = "^3.3.0"

[tool.poetry.scripts]
start = "my_api.main:run"
migrate = "my_api.db:run_migrations"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Managing Dependencies

# Add a runtime dependency
poetry add requests
poetry add "fastapi[all]"          # extras
poetry add "httpx>=0.27,<1.0"     # version constraint

# Add with specific version constraint types
poetry add "pydantic^2.0"         # ^2.0 means >=2.0.0, <3.0.0
poetry add "sqlalchemy~2.0"       # ~2.0 means >=2.0.0, <2.1.0

# Add a dev dependency
poetry add --group dev pytest ruff mypy

# Remove a dependency
poetry remove requests

# Update all dependencies to latest compatible versions
poetry update

# Update a specific package
poetry update fastapi

# Show dependency tree
poetry show --tree

# Check for outdated dependencies
poetry show --outdated

# Export to requirements.txt (for tools that need it)
poetry export -f requirements.txt --output requirements.txt --without-hashes
poetry export -f requirements.txt --only main --output requirements.prod.txt

Dependency Groups

Dependency groups let you separate production, development, testing, and documentation dependencies. Only the [tool.poetry.dependencies] section (the main group) is installed by default in production.

# Install only production dependencies (for Docker/production)
poetry install --only main

# Install main + dev (default for development)
poetry install

# Install main + specific group
poetry install --with test

# Exclude a group
poetry install --without docs

# Install from lock file without updating it (fastest for CI)
poetry install --no-root  # skip installing the package itself
# pyproject.toml — multiple groups
[tool.poetry.group.dev.dependencies]
ruff = "^0.4"
mypy = "^1.10"
ipython = "^8.0"

[tool.poetry.group.test.dependencies]
pytest = "^8.0"
pytest-asyncio = "^0.23"
pytest-cov = "^5.0"
httpx = "^0.27"       # test client for FastAPI

[tool.poetry.group.docs.dependencies]
mkdocs = "^1.6"
mkdocs-material = "^9.0"

Virtual Environments

# List virtualenvs managed by Poetry
poetry env list

# Show current virtualenv info
poetry env info

# Use a specific Python version
poetry env use python3.12
poetry env use /usr/bin/python3.11

# Remove a virtualenv
poetry env remove python3.11

# With virtualenvs.in-project = true, the venv is at .venv/
# VS Code automatically detects .venv/bin/python as the interpreter

# In CI — prefer the system Python and avoid creating a venv
export POETRY_VIRTUALENVS_CREATE=false
poetry install --only main

Publishing to PyPI

# Build the distribution packages
poetry build
# Creates: dist/my-api-1.0.0.tar.gz and dist/my_api-1.0.0-py3-none-any.whl

# Configure PyPI credentials (or use POETRY_PYPI_TOKEN_PYPI env var)
poetry config pypi-token.pypi your-token-here

# Publish to PyPI
poetry publish

# Publish to a private registry
poetry config repositories.mycompany https://pypi.mycompany.com/simple/
poetry config pypi-token.mycompany your-private-token
poetry publish --repository mycompany

# Bump version
poetry version patch    # 1.0.0 -> 1.0.1
poetry version minor    # 1.0.0 -> 1.1.0
poetry version major    # 1.0.0 -> 2.0.0

Docker Workflows

# Multi-stage Dockerfile with Poetry
FROM python:3.12-slim AS builder

# Install Poetry
ENV POETRY_HOME=/opt/poetry
ENV POETRY_VIRTUALENVS_IN_PROJECT=true
ENV POETRY_NO_INTERACTION=1
RUN pip install poetry==1.8.3

WORKDIR /app
COPY pyproject.toml poetry.lock ./

# Install only production dependencies
RUN poetry install --only main --no-root

# Production stage — copy only what's needed
FROM python:3.12-slim AS production
WORKDIR /app

# Copy virtualenv from builder
COPY --from=builder /app/.venv .venv
COPY my_api/ my_api/

ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

EXPOSE 8000
CMD ["uvicorn", "my_api.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Lock file in version control: Always commit poetry.lock to git. It ensures every developer, CI run, and Docker build installs the exact same package versions. Without it, a dependency's dependency could update and break your app silently.

Frequently Asked Questions

Poetry vs pip + venv + requirements.txt — is it worth switching?
Yes for new projects. Poetry's lock file gives you reproducibility that requirements.txt approximates but doesn't guarantee (transitive dependencies can drift). The tradeoff is an extra tool to learn and occasional issues with packages that use non-standard build systems.
How do I migrate from requirements.txt to Poetry?
Run poetry init, then poetry add $(cat requirements.txt). Review the generated pyproject.toml to tighten version constraints. Delete requirements.txt once your team is comfortable with Poetry.
Poetry vs uv — which is faster?
uv (from Astral, makers of Ruff) is 10–100x faster than Poetry for installs because it is written in Rust. As of 2026 uv is production-ready and worth evaluating for CI speed. Poetry has a larger ecosystem and more mature publishing workflow. Many teams use uv for installs and keep Poetry for publishing.