The Day the Monorepo Fought Back
uv Workspace vs Poetry: managing Python monorepos wasn’t something I planned to write about.
It was something I ended up living through.
The moment is still clear:
a harmless change in one service broke tests in three others.
Not because the code was wrong.
Because the environment graph was wrong.
Same repo.
Same commit.
Different behavior depending on where I ran it.
That’s when I realized something uncomfortable:
My monorepo wasn’t complicated because of code.
It was complicated because of tooling assumptions.
Why Python Monorepos Are Genuinely Hard
A Python monorepo isn’t just “multiple projects in one folder”.
It’s usually:
- Shared internal libraries
- Multiple services with overlapping dependencies
- Different release cadences
- Partial installs (you rarely need everything)
Most Python tools were designed for a simple assumption:
one project → one environment → one lockfile
Monorepos break that model immediately.
Why I Started With Poetry
I didn’t choose Poetry by accident.
Poetry felt like the right tool:
- Clean
pyproject.toml - Strict dependency resolution
- Lockfile discipline
- Predictable local environments
At first, it worked.
Then the monorepo grew.
Where Poetry Starts to Struggle
Poetry’s mental model is still fundamentally:
one project, one environment
In a monorepo, this leads to:
- Multiple
poetry.lockfiles - Repeated dependency resolution
- Long install times in CI
- Hard-to-explain cross-package behavior
Poetry wasn’t broken.
It just wasn’t built for this shape of repository.
The Breaking Point: Partial Installs
The real pain showed up when I tried something simple:
“I only want to work on the API service, not the whole repo.”
With Poetry, I had to:
- Install everything
- Or wire path dependencies manually
- Or create separate virtualenvs per service
None of that scaled cleanly.
Discovering uv Workspaces
I didn’t switch tools intentionally.
I discovered uv workspaces while optimizing installs elsewhere.
What caught my attention wasn’t speed — it was the mental model.
No orchestration DSL.
No plugin ecosystem.
Just an explicit workspace definition.
The Core Difference: Mental Models
This is the difference that matters:
Poetry thinks in projects
uv thinks in workspaces
That distinction is subtle — and incredibly important for monorepos.
What a uv Workspace Looks Like
At the repository root:
# pyproject.toml
[tool.uv.workspace]
members = [
"libs/common",
"services/api",
"services/worker"
]Each member has its own pyproject.toml.
uv then generates one unified lockfile at the workspace root:
uv.lockThis ensures:
- Version consistency across all packages
- No duplicated resolution work
- Predictable dependency graphs
Installing Only What You Need
This is where uv won me over.
If the package name in services/api/pyproject.toml is:
[project]
name = "api"You install just that package with:
uv sync --package apior the short form:
uv sync -p apiDependencies resolve once.
Internal libraries are linked automatically.
No full-repo install required.
How Poetry Handles the Same Scenario
Poetry can do this — but with more ceremony.
Example (inside services/api/pyproject.toml):
[tool.poetry.dependencies]
common = { path = "../../libs/common", develop = true }This works, but it introduces:
- Fragile relative paths
- Lockfile drift
- CI install-order sensitivity
It’s manageable — until the repo scales.
Dependency Resolution: Repeated vs Centralized
This difference compounds over time.
Poetry
- Resolves dependencies per project
- Multiple lockfiles repeat work
- CI pays the cost every time
uv Workspace
- Resolves once at workspace level
- Shares results across packages
- Faster, more predictable installs
CI in a Monorepo (Where This Matters Most)
With uv, I can be explicit:
# Install dependencies for API only
uv sync --package api
# Run API tests only
uv run --package api pytest tests/Local and CI behavior match exactly.
No hidden environment differences.
Migration Journey (No Big Bang)
I didn’t remove Poetry overnight.
Phase 1: Parallel Setup
Phase 2: One Package at a Time
Phase 3: Workspace Lockfile
No freeze weeks.
No forced rewrites.
uv Workspace Limitations (Be Honest)
uv workspaces do have constraints:
- All workspace members must live inside the workspace root
- You can’t reference packages outside the workspace
- One lockfile means coordinated upgrades
For real monorepos, these are usually features, not drawbacks.
When Poetry Still Makes Sense
Poetry is still excellent when:
- You have a single project
- You publish directly to PyPI
- You want strict project isolation
When uv Workspace Clearly Wins
uv shines when:
- You share internal libraries
- You want partial installs
- You want one dependency graph
- You want CI and local to behave identically
That’s most real monorepos.
Final Thoughts
uv Workspace vs Poetry: managing Python monorepos isn’t about declaring a winner.
It’s about choosing the tool whose assumptions match your repository.
Poetry assumes isolated projects.
uv assumes connected ones.
Once my repo crossed that line, the decision became obvious.
I stopped fighting my tooling.
I stopped explaining weird environment behavior.
And my monorepo finally felt… boring.
That’s the best outcome I could ask for.
