Table of Contents

Monorepos look attractive—one repository, shared tooling, consistent standards.
Then you try to build one with Poetry… and reality hits.
A poetry monorepo can be elegant and reliable, but only if you understand its rules. Poetry is unforgiving. It rewards discipline and punishes shortcuts. This guide explains what actually works, what commonly fails, and how to structure a poetry monorepo without fighting the tool.


What Is a Poetry Monorepo?

A poetry monorepo is a single Git repository that contains multiple Python packages, each managed by Poetry using its own pyproject.toml.
Typical use cases include:

  • Shared internal libraries
  • Multiple services with common dependencies
  • Data + API + worker projects in one repo
  • Versioned internal packages reused across teams

Poetry handles dependency resolution, locking, and packaging — but it does not provide native workspace support like npm or Yarn. That’s where many mistakes begin.
For deeper details on dependency resolution and locking behavior, the official Poetry documentation provides authoritative guidance.


Why Teams Choose a Poetry Monorepo

Despite its strictness, a poetry monorepo is appealing because it provides:

  • Deterministic dependency resolution
  • Reproducible environments via poetry.lock
  • Clean separation between packages
  • Explicit dependency relationships
  • Strong version constraints

As repositories grow, selecting the right Python package manager becomes a foundational decision for long-term stability.
When done right, a poetry monorepo eliminates “works on my machine” issues almost entirely.


The Correct Poetry Monorepo Structure

The safest and most common structure looks like this:

repo/
├── packages/
│   ├── core/
│   │   ├── pyproject.toml
│   │   └── poetry.lock
│   ├── api/
│   │   ├── pyproject.toml
│   │   └── poetry.lock
│   └── worker/
│       ├── pyproject.toml
│       └── poetry.lock
└── README.md

Key Rule

Each package is a fully independent Poetry project.
There is no shared root poetry.lock in a healthy poetry monorepo.


Internal Dependencies the Right Way

In a poetry monorepo, packages depend on each other using path dependencies.

Example (api/pyproject.toml):

[tool.poetry.dependencies]
python = "^3.10"
core = { path = "../core", develop = true }

What this does:

  • Links the local package
  • Avoids publishing during development
  • Keeps dependency resolution explicit
  • Works cleanly in CI when structured correctly

Never treat internal packages as folders. They are real packages.


Lock Files: One of the Biggest Mistakes

A common misconception:

“Can we use one lock file for the whole monorepo?”

In practice, no.
Poetry resolves dependencies per project. A single shared lock file quickly becomes inconsistent and fragile.

Best practice:

  • One poetry.lock per package
  • Regenerate locks independently
  • Never manually edit lock files

This is non-negotiable in a stable poetry monorepo.


CI/CD in a Poetry Monorepo

A real CI pipeline installs and tests packages individually.

Example (per package):

cd packages/api
poetry install
poetry run pytest

Advanced setups may:

  • Detect changed packages
  • Test only affected projects
  • Cache Poetry environments

But the core idea remains: each package owns its lifecycle.


Tooling Reality: Poetry Alone Is Not Enough

Poetry does not manage orchestration across packages.

For large monorepos, teams often add:

  • Custom shell scripts
  • Makefiles
  • Task runners
  • Tools like Pants or Bazel (for very large repos)

A poetry monorepo works best when:

  • Poetry manages packages
  • Another layer manages orchestration

Python Version Alignment Is Mandatory

All projects in a poetry monorepo must agree on Python compatibility:

python = "^3.10"

Misaligned Python versions cause:

  • Dependency conflicts
  • Lock failures
  • CI inconsistencies

Decide once. Enforce everywhere.


When a Poetry Monorepo Makes Sense

A poetry monorepo is a good choice if:

  • You need strict dependency control
  • You value reproducibility over convenience
  • Your team respects tooling boundaries
  • Internal packages evolve together

It’s a poor choice if:

  • You want plug-and-play flexibility
  • You frequently mix package managers
  • You expect workspace magic out of the box

Common Mistakes That Break a Poetry Monorepo

Let’s talk about what not to do.

  • Mixing pip and Poetry
  • Editing poetry.lock manually
  • Treating internal packages like folders
  • Ignoring Python version alignment

A poetry monorepo is strict—but fair.


FAQ: Common Mistakes That Break a Poetry Monorepo

Can I use a single poetry.lock file for the entire monorepo?

No. Poetry resolves dependencies per project. Using a single lock file leads to conflicts and unstable builds. Each package should maintain its own poetry.lock.

Is it okay to mix pip and Poetry inside a monorepo?

No. Mixing package managers causes dependency drift and breaks reproducibility. Choose Poetry and use it consistently.

Can I manually edit poetry.lock to fix issues?

Never. Manually editing poetry.lock almost guarantees broken dependency resolution. Always regenerate it using Poetry commands.

Why can’t I treat internal packages as simple folders?

Because they are real Python packages. They must be versioned, declared as dependencies, and resolved explicitly to maintain correctness.

Do all projects need the same Python version?

Yes. All packages in a poetry monorepo must align on Python compatibility to avoid resolution and CI failures.

Final Thoughts

A poetry monorepo is not forgiving—but it is predictable.
If you respect Poetry’s model:

  • Independent packages
  • Explicit dependencies
  • Separate lock files
  • Consistent Python versions

You get stability, clarity, and reproducibility.
If you fight it, the monorepo will fight back harder.
That’s not a flaw.
That’s the contract.