Beginner

If you’ve ever cloned a Python project and spent an hour figuring out which packages to install, which versions are compatible, and why things work on your machine but not your colleague’s — you understand exactly why dependency management matters. The traditional combo of pip plus a hand-maintained requirements.txt works for simple scripts, but it breaks down quickly on real projects with dozens of dependencies and multiple contributors.

Poetry solves this by giving you a single tool that handles virtual environment creation, dependency resolution, version pinning, and package publishing — all driven by a single pyproject.toml file. Poetry is not the only tool in this space (pip-tools, pipenv, and uv all solve overlapping problems), but it has become one of the most widely adopted thanks to its clean CLI, deterministic lockfile, and first-class support for building and publishing packages to PyPI.

In this tutorial you’ll install Poetry, create a new project from scratch, add and manage dependencies, understand the lockfile, work with environments, and package your project for distribution. By the end you’ll have a complete workflow that replaces scattered requirements files with a reproducible, shareable project setup.

Python Poetry: Quick Example

Here is a minimal workflow — create a project, add a dependency, and run a script — to show how Poetry feels in practice.

# In your terminal (not Python) -- these are shell commands

# Install Poetry (run once)
curl -sSL https://install.python-poetry.org | python3 -

# Create a new project
poetry new myproject
cd myproject

# Add a dependency
poetry add requests

# Run a Python script inside Poetry's managed environment
poetry run python -c "import requests; print(requests.get('https://httpbin.org/get').status_code)"

Output:

200

The poetry new command creates a project directory with a pyproject.toml, a README.md, and a package skeleton. poetry add requests installs requests into a dedicated virtual environment, records it in pyproject.toml, and pins the exact version in poetry.lock. poetry run executes any command inside that managed environment without needing to manually activate it.

What is Poetry and Why Use It?

Poetry is a Python packaging and dependency management tool that uses pyproject.toml as its single source of truth. It replaces several tools that traditionally had to be combined: pip for installation, venv for environments, and setuptools for packaging. Poetry handles all three, plus it generates a lockfile that pins every transitive dependency to an exact version.

TaskTraditional approachPoetry approach
Install packagespip install packagepoetry add package
Create virtual envpython -m venv .venvAutomatic on first add/install
Pin versionspip freeze > requirements.txtpoetry.lock (automatic)
Dev vs prod depsrequirements-dev.txt separatelypoetry add –group dev package
Build + publishsetuptools + twine (separate)poetry build && poetry publish
Reproduce envpip install -r requirements.txtpoetry install

The lockfile is Poetry’s killer feature. When you run poetry add, Poetry solves the entire dependency graph — including every indirect dependency — and records the exact version of every package in poetry.lock. Anyone who clones your repo and runs poetry install gets the exact same environment, down to patch versions. This makes “it works on my machine” problems much rarer.

Tutorial image
poetry.lock: because ‘it worked yesterday’ is not a deployment strategy.

Installing Poetry and Setting Up a Project

The recommended way to install Poetry is the official installer, which places it in its own isolated environment separate from any project. This prevents Poetry itself from interfering with your project’s dependencies.

# install_poetry.sh -- run in terminal

# Install Poetry (Linux/macOS/WSL)
curl -sSL https://install.python-poetry.org | python3 -

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

# Verify installation
poetry --version
# Poetry (version 1.8.x)

# Configure Poetry to create virtual envs inside the project folder
# This makes it easier to see and delete the env
poetry config virtualenvs.in-project true

Output:

Poetry (version 1.8.3)

After installation, you may need to add Poetry’s bin directory to your PATH. The installer prints the exact path — typically ~/.local/bin on Linux/macOS. The virtualenvs.in-project true setting is optional but recommended: it creates the virtual environment in a .venv/ folder inside your project directory, which makes it visible in your IDE and easy to delete when switching Python versions.

Creating a New Project vs Initializing an Existing One

Poetry has two modes for starting: poetry new creates a fresh project directory from a template, and poetry init adds Poetry to an existing directory interactively.

# Terminal commands

# Start a brand new project
poetry new mywebapp
# Creates:
#   mywebapp/
#   mywebapp/pyproject.toml
#   mywebapp/README.md
#   mywebapp/mywebapp/__init__.py
#   mywebapp/tests/__init__.py

# OR add Poetry to an existing project
cd existing_project
poetry init
# Interactive prompts ask for project name, version, description,
# Python version constraint, and initial dependencies

# After either approach, install dependencies (creates the venv if needed)
poetry install

Output after poetry install:

Creating virtualenv mywebapp-2T5b5b6K-py3.11 in /home/user/.cache/pypoetry/virtualenvs
Installing dependencies from lock file

Package operations: 0 installs, 0 updates, 0 removals

The generated pyproject.toml contains everything Poetry needs to know about your project: name, version, description, Python version requirements, and dependencies. Commit this file and poetry.lock to version control — both are essential for reproducibility.

Adding, Updating, and Removing Dependencies

All dependency management flows through the poetry add, poetry update, and poetry remove commands. Poetry resolves the full dependency graph on every operation and updates the lockfile accordingly.

# Terminal commands for dependency management

# Add a runtime dependency
poetry add fastapi

# Add with version constraint
poetry add "sqlalchemy>=2.0,<3.0"

# Add a development-only dependency (not installed in production)
poetry add --group dev pytest pytest-cov black ruff

# Add an optional dependency (user must opt in)
poetry add --optional pandas

# See what's installed
poetry show

# See dependency tree (what depends on what)
poetry show --tree

# Update a specific package to the latest allowed version
poetry update requests

# Update ALL packages
poetry update

# Remove a package
poetry remove httpx

Output of poetry show --tree (excerpt):

fastapi 0.111.0 FastAPI framework
|-- anyio >=3.7.1,<5
|   |-- idna >=2.8
|   `-- sniffio >=1.1
|-- pydantic >=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0
|   |-- annotated-types >=0.4.0
|   `-- pydantic-core 2.18.2
`-- starlette >=0.37.2,<0.38.0

The --group dev flag separates development tools from production dependencies. When you deploy your application and run poetry install --without dev, dev dependencies are skipped, keeping your production image lean. The dependency tree view is invaluable for understanding why a particular package version is installed -- if a constraint is preventing an upgrade, the tree shows you which package is imposing it.

Tutorial image
poetry show --tree: finally understanding why numpy is v1.24 and not v2.

Working with Environments

Poetry automatically creates and manages a virtual environment for each project. You do not need to run python -m venv or source .venv/bin/activate manually. Instead, prefix commands with poetry run or drop into the environment with poetry shell.

# Terminal environment commands

# Run a single command inside Poetry's environment
poetry run python script.py
poetry run pytest
poetry run python -m flask run

# Activate the environment in your current shell session
poetry shell
# Now python, pip, and any installed scripts are from Poetry's env
# Type 'exit' or Ctrl-D to leave the shell

# See where the virtual environment lives
poetry env info
# Output:
# Virtualenv
# Python:         3.11.9
# Implementation: CPython
# Path:           /home/user/mywebapp/.venv
# Executable:     /home/user/mywebapp/.venv/bin/python

# Use a specific Python version (must be installed on your system)
poetry env use python3.12

# List all environments Poetry knows about for this project
poetry env list

# Delete the current environment (useful when switching Python versions)
poetry env remove python3.11

Output of poetry env info (excerpt):

Virtualenv
Python:         3.11.9
Implementation: CPython
Path:           /home/user/mywebapp/.venv
Executable:     /home/user/mywebapp/.venv/bin/python
Valid:           True

Most IDEs (VS Code, PyCharm) can detect Poetry environments automatically when virtualenvs.in-project is set to true -- just point the IDE at .venv/bin/python or select it from the interpreter picker. For CI/CD pipelines, use poetry run pytest rather than activating the shell, since shell activation can behave differently across CI environments.

Real-Life Example: Setting Up a FastAPI Project with Poetry

Tutorial image
One toml to rule them all, one lockfile to find them.

Here is a complete example of bootstrapping a FastAPI project with Poetry, including runtime dependencies, dev tooling, and a script entrypoint. This is the setup pattern used in real production Python services.

# Terminal commands to bootstrap a FastAPI project

poetry new fastapi-demo
cd fastapi-demo

# Add runtime dependencies
poetry add fastapi "uvicorn[standard]" pydantic-settings

# Add dev dependencies
poetry add --group dev pytest httpx pytest-asyncio ruff black

# Check the generated pyproject.toml
cat pyproject.toml

pyproject.toml output:

[tool.poetry]
name = "fastapi-demo"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"
fastapi = "^0.111.0"
uvicorn = {extras = ["standard"], version = "^0.29.0"}
pydantic-settings = "^2.2.1"

[tool.poetry.group.dev.dependencies]
pytest = "^8.2.0"
httpx = "^0.27.0"
pytest-asyncio = "^0.23.0"
ruff = "^0.4.0"
black = "^24.4.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
# fastapi_demo/main.py
from fastapi import FastAPI

app = FastAPI(title="Demo API")

@app.get("/health")
async def health():
    return {"status": "ok"}
# Run the app
poetry run uvicorn fastapi_demo.main:app --reload

# Run tests
poetry run pytest -v

# Lint and format
poetry run ruff check .
poetry run black .

This pattern keeps everything reproducible. A new developer clones the repo, runs poetry install, and gets the exact same environment with all runtime and dev dependencies. The lockfile (poetry.lock) guarantees no version surprises, and the pyproject.toml serves as the single source of truth for project metadata, dependencies, and tool configuration.

Frequently Asked Questions

Should I commit poetry.lock to version control?

Yes, always commit poetry.lock for applications. It ensures every developer and every CI run uses identical package versions. For libraries that other projects depend on, the recommendation is more nuanced -- many library maintainers still commit the lockfile to pin CI test dependencies, but the lockfile is not used when someone installs your library as a dependency. The pyproject.toml version constraints are what matter for library consumers.

Can I export a requirements.txt from Poetry?

Yes: run poetry export -f requirements.txt --output requirements.txt --without-hashes. This is useful when deploying to environments that don't have Poetry installed (e.g., a Docker container that uses pip). The --without-hashes flag makes the file compatible with plain pip. For dev requirements, add --with dev to the export command.

How do I change the Python version for a Poetry project?

First install the target Python version on your system (via pyenv, asdf, or your OS package manager). Then run poetry env use python3.12 to point Poetry at the new interpreter. Poetry will create a fresh virtual environment for that version. Update the python = "^3.12" constraint in pyproject.toml if needed, then run poetry install to reinstall all dependencies.

How do I use Poetry in GitHub Actions?

Install Poetry as a step before your Python setup, then use poetry install to install dependencies. Cache the virtual environment using the lockfile hash as the cache key: hashFiles('poetry.lock'). The official Poetry documentation provides ready-to-use GitHub Actions workflow examples. Alternatively, use the popular snok/install-poetry action which handles installation and PATH setup automatically.

Poetry vs pip-tools vs uv -- which should I use?

All three solve dependency locking but have different strengths. Poetry is the most full-featured: it handles environments, dependencies, and building/publishing in one tool, making it great for both applications and libraries. pip-tools is minimal and stays close to pip -- good if you want explicit control and a lighter tool. uv is extremely fast (Rust-based) and is gaining rapid adoption for its speed; it now has a compatibility mode for pyproject.toml projects. For new projects in 2026, Poetry or uv are both solid choices.

Conclusion

Poetry simplifies Python project management by replacing the fragmented combination of pip, venv, and requirements files with a single unified tool driven by pyproject.toml. You learned to create projects with poetry new, add dependencies with poetry add, separate dev and production requirements with dependency groups, inspect the dependency tree, manage virtual environments, and set up a complete FastAPI project using the Poetry workflow.

The reproducibility that comes from committing poetry.lock to version control is the biggest practical benefit -- it eliminates an entire category of "works on my machine" bugs and makes onboarding new team members faster. Try converting one of your existing projects to Poetry by running poetry init in the project root and migrating your existing requirements files.

Find the full Poetry documentation and a migration guide from pip at python-poetry.org/docs.