---
title: "Poetry 2.x Docker Breaking Changes"
aliases: [poetry-docker, poetry-install-no-root, poetry-lock-mismatch]
tags: [poetry, docker, python, packaging, breaking-change]
sources:
  - "daily/2026-04-29.md"
created: 2026-04-29
updated: 2026-05-08
---

# Poetry 2.x Docker Breaking Changes

Poetry 2.x changed the default behaviour of `poetry install` — if `pyproject.toml` declares a `readme` field, the install fails unless `--no-root` is passed, because Poetry 2.x tries to build the package itself as part of installation. A second silent failure mode exists when the `poetry.lock` file was generated on a developer machine running a different Poetry version from the one in the Dockerfile — the lock file format version differs and Poetry exits with a non-zero code that can be swallowed by Docker layer caching.

## Key Points

- `poetry install` without `--no-root` fails when `pyproject.toml` has `readme = "README.md"` (or any path) under `[tool.poetry]`
- `poetry.lock` contains a `lock-version` field — Poetry 2.x uses `"2.1"`, Poetry 1.x used `"1.0"`; mixing versions causes silent lock file errors
- The symptom is a container that builds successfully but crashes at runtime with `ModuleNotFoundError` — the install appeared fine but packages were never actually installed
- Always pin the Poetry version in the Dockerfile to match the dev machine: `pip install poetry==2.1.1`

## Details

### The `--no-root` Fix

```dockerfile
# BAD — fails when pyproject.toml has a readme or package declaration
RUN poetry install

# GOOD — installs dependencies only, skips building the project package
RUN poetry install --no-root
```

The `--no-root` flag tells Poetry "install the dependencies listed in pyproject.toml, but do not install the root package itself." This is almost always what you want in a Docker context where you're deploying an application, not a library.

### Pinning Poetry Version

```dockerfile
FROM python:3.12-slim

# Pin to exact same version as dev machine
RUN pip install --no-cache-dir poetry==2.1.1

WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN poetry install --no-root --no-dev

COPY . .
CMD ["poetry", "run", "uvicorn", "app.main:app"]
```

### Lock File Version Mismatch Diagnosis

```bash
# Check lock file version
head -5 poetry.lock
# lock-version = "2.1"   ← Poetry 2.x
# lock-version = "1.0"   ← Poetry 1.x

# Check dev machine version
poetry --version
# Poetry (version 2.1.1)

# Regenerate lock file after upgrading/downgrading
poetry lock --no-update
```

### pyproject.toml Triggering the Bug

```toml
[tool.poetry]
name = "my-app"
version = "0.1.0"
readme = "README.md"   # ← This line causes poetry install (no --no-root) to fail in Poetry 2.x
```

## Server-Side poetry.lock Merge Conflict

After running `poetry update` on the server (e.g., to install a new package), `git pull` will fail with a merge conflict on `poetry.lock` — the server's updated lock diverges from the committed one.

```bash
# Error: CONFLICT (content): Merge conflict in backend/poetry.lock

# Fix: discard the server-side lock, use the committed version
git checkout backend/poetry.lock
git pull

# Why: the committed lock is authoritative; server-side poetry updates
# should never be done directly — update locally, commit, then deploy
```

The root cause is treating the server's working directory as a place to run `poetry update`. Always run `poetry update` locally, commit the updated `poetry.lock`, and let the deploy pipeline rebuild the container.

## Related Concepts

- [[wiki/tech-patterns/fastapi-python-docker]] — the main deployment pattern where this issue surfaces
- [[wiki/concepts/python-service-deployment-dotenv]] — broader Python service deploy checklist

## Sources

- [[daily/2026-04-29.md]] — Discovered during Docker container crash debugging on a FastAPI service
- [[daily/2026-05-08.md]] — Server-side poetry.lock conflict pattern discovered during TTS migration deploy
