poetry add vs pip install: When Lock Files Prevent Production Dependency Conflicts
When we manage Python dependencies, we aim for reliable production deployments. pip install can lead to version conflicts — what some call “dependency hell” — where transitive dependencies cause builds to fail in CI/CD pipelines or staging environments. Tools like Poetry offer a declarative approach with pyproject.toml for declarations and poetry.lock for pinning resolved versions, helping to create more reproducible environments.
In this guide, we’ll compare poetry add and pip install, examine how lock files help manage dependencies, and discuss steps for using Poetry along with other tools.
What is Dependency Hell and Why Does It Plague Production?
You might encounter dependency hell when transitive dependencies conflict. For example:
- Package A requires
numpy >=1.20 - Package B requires
numpy <1.22
pip install resolves at install time based on the current environment, but:
# On dev machine (numpy 1.21 works)
$ pip install package-a package-b
# In production Docker build (different resolver order)
$ pip install package-b package-a # Fails: numpy conflict
This can lead to failed deploys and debugging time. Lock files address this by capturing the exact resolved versions and hashes used.
How Pip Install Works
When you run pip install, it installs packages directly into your environment:
pip install requests→ Adds tosite-packages, updatespip freeze.- Lock:
pip freeze > requirements.txt(unpinned versions unless manual). - Install:
pip install -r requirements.txt.
Considerations:
requirements.txttypically uses loose constraints (e.g.,requests>=2.25), which can allow version drift over time.- Without hashes, there’s risk from supply chain attacks, though tools like pip-tools can add them.
- Resolver behavior can vary across Python versions or OS.
This leads to the common “It works on my machine” issue in production.
Poetry Add: Declarative with Lock Files
Poetry uses pyproject.toml for dependency declarations and poetry.lock to record the resolved versions:
poetry add requests→ Updates[tool.poetry.dependencies]andpoetry.lock.poetry.lockpins exact versions + hashes (SHA256).- Install:
poetry install→ Reproducible, ignores PyPI changes.
Example pyproject.toml:
[tool.poetry.dependencies]
python = "^3.11"
requests = "^2.31"
numpy = "^1.24"
Snippet from poetry.lock:
[[package]]
name = "requests"
version = "2.31.0"
source = {registry = "https://pypi.org/simple/"}
dependencies = [
{name = "charset-normalizer", version = "3.3.2"},
]
files = [
{file = "requests-2.31.0.tar.gz", hash = "sha256:..."},
]
Poetry offers:
- Reproducible installs via
poetry install. - Content-addressed hashes for verification.
- Separate groups for dev and prod dependencies (e.g.,
poetry add --group dev pytest). - Lock file that you commit to version control.
Comparing Poetry and Pip
| Aspect | Pip with requirements.txt | Poetry with pyproject.toml/poetry.lock |
|---|---|---|
| Workflow | Imperative (install modifies env) | Declarative (lock resolved versions) |
| Lock File | Loose constraints (needs pip-compile for pins/hashes) | Exact versions + hashes |
| Reproducibility | Variable; better with pinned reqs | High, via committed lock |
| Prod Safety | Relies on resolver; hashes optional | Hashes included; verifies integrity |
| Conflict Resolution | At install time | At lock time |
Example Comparison:
# Pip
$ echo "requests>=2.31 numpy>=1.24" > requirements.txt
$ pip install -r requirements.txt
# Poetry
$ poetry add requests numpy
$ poetry install
Both can work locally, but reproducibility depends on pinning in pip case.
Real-World Production Conflict: How Poetry Fixes It
Consider this scenario: an ML app with tensorflow (needs old numpy) and pandas (needs new).
With pip, builds fail sporadically.
With Poetry:
$ poetry init
$ poetry add tensorflow pandas # Resolves compatible versions
$ git add pyproject.toml poetry.lock
# Team/CI: poetry install → Same env
Dockerfile:
COPY pyproject.toml poetry.lock ./
RUN pip install poetry && poetry install --no-dev --no-interaction
Conflicts are resolved at lock time, reducing surprises.
Other Approaches to Dependency Locking
While Poetry provides a full-featured solution, other tools address similar concerns:
Pip-tools (pip-compile):
- Generates pinned
requirements.txtfrom looserequirements.in. - Adds hashes for security.
- Trade-off: Separate lock step; integrates with existing pip workflows.
uv:
- Fast resolver and installer.
uv pip syncfor reproducible envs from lockfiles.- Trade-off: Newer tool; lighter than full Poetry but less mature ecosystem.
Choose based on your needs: Poetry for integrated pyproject.toml management, pip-tools for minimal changes, uv for speed.
Migrating from Pip to Poetry
- Init:
$ poetry init(interactive pyproject.toml). - Add from reqs:
$ poetry add $(grep -v '^#' requirements.txt | cut -d= -f1). - Lock:
$ poetry lock. - Remove pip-tools: Delete
requirements.txt, use$ poetry export -f requirements.txt --output requirements.txtfor Docker if needed. - CI/CD:
$ poetry install. - Virtualenvs:
$ poetry shellor$ poetry run python.
Note: You can configure Poetry to create .venv in your project root with $ poetry config virtualenvs.in-project true.
Best Practices for Lock Files in Production
- Commit
poetry.lock(always). $ poetry lock --no-updatefor reproducible locks.- Use dependency groups: prod/dev/test.
- Audit:
$ poetry check,$ poetry show --tree. - Docker multi-stage: Install prod deps only.
- GitHub Actions: Cache
.venv.
Conclusion
Lock files help manage dependencies reproducibly across tools like pip-compile, Poetry, and uv. We encourage you to evaluate options based on your project’s needs and workflow for more reliable deployments.
<RelatedLinks {relatedLinks} />
Sponsored by Durable Programming
Need help maintaining or upgrading your Python application? Durable Programming specializes in keeping Python apps secure, performant, and up-to-date.
Hire Durable Programming