How to Structure a Python Monorepo for a Growing Engineering Team
Structure a Python monorepo that scales with your engineering team. Real 2026 guide to uv workspaces, Pants, Airflow patterns, and migration strategy.
Acquaint Softtech
Introduction: The Monorepo Decision That Compounds for Years
Most Python codebases start as a single repository, accidentally. Founders build a backend, ship features, hire engineers, and at some point someone asks the question: should we keep adding services to this repo or split them into separate ones? The decision feels small in the moment. It is not. The structure you pick at engineer 5 will define your build times, your deployment frequency, and your hiring bar at engineer 50. Get it right early and the team scales smoothly. Get it wrong and you spend a year of engineering time fixing what should have been infrastructure from day one.
The cost of a bad monorepo setup is well documented. According to a 2026 production migration case study published by Doppel on scaling their Python organisation, their pre-migration setup scaled poorly with team growth: build and test times rose with every new engineer, services that previously took 10+ minutes to deploy required dependency installation of the entire monorepo's superset, and the team deployed 1,656 times in a single month while paying that overhead per deployment. After migrating to uv workspaces, build times dropped 30 to 70% and test times fell by roughly half. Those numbers compound across hundreds of deployments per month and dozens of engineers.
This playbook covers how to structure a Python monorepo that scales with your engineering team in 2026. It is written for tech leads, principal engineers, and CTOs who are either making the polyrepo vs monorepo decision for the first time, planning a migration, or trying to make an existing monorepo stop hurting. Every recommendation below is grounded in production patterns from teams shipping real software, not theoretical ideals.
If you are still building the team that will execute the monorepo, the complete guide to hiring Python developers in 2026 sets the wider hiring context. The patterns below assume you have engineers who can implement them and review them rigorously.
Should You Even Have a Monorepo? The Honest Answer
Before structuring a monorepo, decide whether you actually need one. The 2026 community consensus is that monorepos are a tool, not a destination. They solve specific organisational problems and create specific operational ones. The wrong choice for your team produces months of pain regardless of how well you structure the result.
Industry analysis backs both sides of the debate. According to Aviator's 2026 review of monorepo tools, monorepos unlock significant productivity gains when teams are prepared to invest in infrastructure and disciplined workflows, but Lyft engineer Matt Klein's widely-cited critique cautions that 'at scale, a monorepo must solve every problem that a polyrepo must solve, with the downside of encouraging tight coupling, and the additional herculean effort of tackling VCS scalability'. Both perspectives are correct. The right answer depends on your team's specific position on the trade-off.
Table : Monorepo vs Polyrepo Decision Matrix for Python Teams
Dimension | Choose Polyrepo | Choose Monorepo |
|---|---|---|
Team size | Under 5 engineers | 10+ engineers |
Service count | 1 to 3 services | 5+ services |
Deployment cadence | Same release rhythm | Different per service |
Code sharing | Minimal cross-service code | Shared libraries common |
Refactoring frequency | Rare cross-service changes | Frequent atomic changes |
DevOps maturity | Basic CI/CD only | Build caching, affected detection |
AI agent tooling use | Optional | Significant productivity gain |
The 2026 Recommended Python Monorepo Structure
A well-structured Python monorepo separates applications from shared libraries, treats every package as independently versioned, and uses a single lockfile at the root for deterministic builds. This pattern is what Apache Airflow, Doppel, and most modern Python organisations have converged on in 2026.
The Canonical Folder Layout
monorepo/
├── apps/ # Deployable services
│ ├── api-gateway/
│ ├── billing-service/
│ ├── notification-service/
│ └── ml-inference/
├── libs/ # Shared internal libraries
│ ├── auth/ # JWT, session, RBAC
│ ├── db/ # Database models, migrations
│ ├── observability/ # Logging, tracing, metrics
│ └── domain/ # Shared business types
├── tools/ # Internal CLI scripts
├── tests/ # Cross-package integration tests
├── pyproject.toml # Workspace root
├── uv.lock # Single lockfile
└── .github/workflows/ # CI configurationThe two top-level distinctions matter. apps/ contains code that ships to production as a deployable unit (a service, a CLI, a worker). libs/ contains code that is imported by apps but never deployed standalone. The split prevents the most common monorepo failure mode: shared libraries that accidentally become services because nobody enforced the boundary.
Tooling: Why uv Workspaces Win in 2026
Python's tooling story has finally caught up with what Rust, Go, and JavaScript teams have had for years. uv workspaces, introduced by Astral (the team behind Ruff), brought Cargo-style monorepo support to Python with a single tool that replaces pip, pip-tools, pipx, poetry, and pyenv. For most teams in 2026, uv is the default starting point for new monorepos.
The Workspace Configuration
# pyproject.toml at monorepo root
[project]
name = "my-monorepo"
version = "0.1.0"
requires-python = ">=3.13"
[tool.uv.workspace]
members = [
"apps/*",
"libs/*",
]
[tool.uv]
dev-dependencies = [
"pytest>=8.0",
"ruff>=0.7",
"mypy>=1.13",
]
[tool.uv.sources]
auth = { workspace = true }
db = { workspace = true }The members entry tells uv which directories contain workspace packages. The sources block declares that internal libraries should resolve from the workspace, not from PyPI. A single uv.lock file at the workspace root provides deterministic, reproducible builds across all packages. Operations that took 30+ seconds with pip complete in under a second with uv.
When uv Workspaces Are Not Enough: Pants for Larger Teams
uv covers most teams up to 30-50 engineers. Beyond that, you usually need a real build system. The most public reference point in 2026 is Apache Airflow. According to a March 2026 PyDevTools writeup of the FOSDEM 2026 talk by Airflow PMC member Jarek Potiuk, Airflow ships over 120 separate Python distributions from a single repository, with 700+ dependencies and 3,600+ contributors. uv sync resolves over 900 packages in seconds. When a developer works on a specific provider, uv automatically installs only the dependencies needed for that distribution. At that scale, uv handles workspace dependency resolution, but Airflow still leans on additional build orchestration patterns.
When to Reach for Pants
100+ deployable services or libraries. uv workspaces handle the dependency layer well, but build graph orchestration becomes a real workload at this scale.
Affected-only test execution required. Pants natively detects which tests are affected by a code change and runs only those, reducing CI time dramatically. uv does not yet do this.
Polyglot codebase. Pants and Bazel handle Python alongside Go, Java, Node.js, and others in a single coherent graph. uv is Python-only.
Aggressive remote build caching needed. Pants and Bazel offer mature remote caching that lets CI machines share build artifacts. uv has fewer cache layers.
For most teams, uv workspaces are the right answer. Pants becomes worth the migration cost only when the team is already large enough that the engineering cost of slow CI is bigger than the cost of adopting a complex build system. Start simple. Migrate up when measurement justifies it.
Code Ownership: The Discipline Without Which Monorepos Fail
A monorepo without code ownership is a tragedy of the commons waiting to happen. Without enforced ownership, every engineer feels free to modify any file, no team feels responsible for any module, and the codebase decays into shared mediocrity. The fix is non-optional: every directory has an owning team, every owner approves changes to their files, and the CI enforces it.
The CODEOWNERS Pattern
asyncio.to_thread() for occasional blocking calls. The simplest option for one-off legacy SDK calls or short CPU work. Runs the function in a thread pool managed by asyncio without blocking the loop.
ProcessPoolExecutor for heavy CPU work. Use loop.run_in_executor with a process pool for image processing, encryption, or any CPU-bound operation. Threads do not help with CPU due to the GIL; processes do.
Celery, RQ, or Dramatiq for true background work. If the work takes more than a few hundred milliseconds, do not offload inline. Send it to a worker queue and respond immediately to the client.
Practical Offload Pattern
# .github/CODEOWNERS
# Default ownership - any unspecified file
* @company/platform-team
# App-specific ownership
/apps/billing-service/ @company/billing-team
/apps/ml-inference/ @company/ml-team
# Shared library ownership
/libs/auth/ @company/security-team
/libs/db/ @company/platform-team
/libs/observability/ @company/sre-team
# Cross-cutting concerns
/.github/workflows/ @company/devops-team
/pyproject.toml @company/platform-teamWith CODEOWNERS, GitHub or GitLab automatically requires the right team's approval on any pull request that touches their files. The mechanism is simple. The cultural shift is harder. Senior engineers must consistently refuse to approve changes outside their domain, and the platform team must defend the boundary even when shipping pressure is high.
Code ownership sits inside the broader architectural discipline covered in the guide on Python development architecture and frameworks, which walks through how module boundaries, dependency direction, and team alignment fit together in a complete Python design.
Need Senior Python Engineers Who Have Built Production Monorepos?
Acquaint Softtech provides senior Python engineers with hands-on experience in uv workspaces, Pants, monorepo CI/CD design, dependency graph optimisation, and migration from polyrepo to monorepo at scale. Profiles in 24 hours. Onboarding in 48.
CI/CD: The Make-or-Break Layer of Any Monorepo
A monorepo without smart CI is a polyrepo's worst nightmare combined with a monolith's slowest deploys. The whole point of monorepo tooling is to run only what changed, build only what depends on it, and deploy only what was actually modified. Without that, every commit triggers a full-repo build and the team's productivity collapses under their own infrastructure.
The Five CI/CD Patterns That Make Monorepos Fast
Path-based triggers. CI workflows run only when files in their watched paths change. A pull request touching apps/billing/ runs billing tests, not the entire repo's test suite.
Affected-only test execution. Use pytest with markers tied to package boundaries, or move to Pants for native affected detection. Running 5,000 tests when 50 are affected wastes hours daily.
Per-service Docker images. Each service builds a minimal image with only its own dependencies. Doppel reduced 10+ minute deploys to under 4 minutes by stopping the practice of installing the entire monorepo's superset of dependencies per service.
Build caching at the CI layer. GitHub Actions cache, GitLab cache, or remote build cache for Pants. Skip work you have already done.
Parallel test execution by default. pytest-xdist or pytest-parallel split tests across CI workers. A 30-minute test suite becomes 5 minutes on 6 parallel workers.
Migration: From Polyrepo Chaos to Monorepo Order
Most teams hit this problem with an existing polyrepo sprawl. Migrating to a monorepo is a multi-quarter investment that produces no visible features for users. Plan for it accordingly. The teams that succeed run migration as a structured program, not a side project.
The Phased Migration Pattern
Table : Phased Polyrepo to Monorepo Migration Plan
Phase | Duration | Key Activities |
|---|---|---|
Discovery and tooling decision | 2 to 4 weeks | uv vs Pants choice, CI prototyping |
Empty monorepo setup | 1 to 2 weeks | Folder layout, uv workspace, CODEOWNERS |
Migrate first 1 to 2 small services | 3 to 4 weeks | Validate the pattern end to end |
Migrate shared libraries | 2 to 3 months | Extract to libs/, refactor imports |
Migrate remaining services | 2 to 4 months | Service per sprint, parallel where safe |
Decommission old repos | 1 month | Archive, redirect, retire |
Total migration timeline for a team with 10 to 30 services typically runs 6 to 12 months. Compressing this timeline produces broken builds, lost git history, and team frustration that lasts longer than the migration itself. Move steadily, not quickly.
For the full ownership cost picture across a multi-quarter monorepo migration, including engineering capacity, infrastructure, and team velocity impact, the analysis on ownership cost of Python projects walks through the 24-month total cost of ownership in detail.
Anti-Patterns That Kill Monorepos in Practice
Some monorepo patterns look productive in the short term and destroy velocity in the long term. The mistakes below are the ones experienced platform teams catch and growing teams ship without realising.
Cross-importing between apps/. Two apps importing each other's modules turns the repo into a distributed monolith. Apps depend only on libs/, never on other apps. Enforce with import linters.
Shared utility dumping ground. A single libs/utils/ where everyone tosses code becomes the codebase's worst section in 12 months. Force naming and ownership at library creation.
Single mega-image Docker builds. Building one Docker image with the union of all dependencies wastes minutes per deployment. Build per-service images with only their own deps.
No import linting. Without tools like import-linter or grimp enforcing the dependency graph, libraries gradually depend on apps and the boundary collapses.
Skipping CODEOWNERS until later. Adding ownership after the codebase is already messy is 10x harder than starting with it. Set up CODEOWNERS on day one.
These anti-patterns are common contributors to expensive Python rebuilds and architecture rewrites, mapping closely to the warning signs covered in the guide on Python development expensive red flags.
What to Look for When Hiring Engineers for a Python Monorepo
Monorepo experience is one of the most under-asked-for skills in Python interviews. Most candidates have worked in either a single-service polyrepo or a chaotic multi-service one. Identifying engineers who have actually shipped in a disciplined monorepo separates senior platform thinkers from generalists.
Have they written a CODEOWNERS file? Direct experience setting up enforced ownership reveals platform-level thinking, not just feature delivery.
Can they explain affected-only test execution? Knowledge of Pants, Nx, or pytest-driven path detection signals real CI/CD optimisation experience.
Have they used uv workspaces or Pants? Direct hands-on with modern tooling separates 2026-current engineers from those still on Poetry or pip-tools.
Can they describe a real migration they led? Specific stories about polyrepo to monorepo migrations, with what worked and what broke, predict real platform capability.
For the broader hiring framework that surrounds these technical signals, especially when working with offshore or contracted Python platform teams, the guide on red flags when outsourcing Python development covers how to spot weak technical claims before they become delivery problems.
How Acquaint Softtech Builds Production Python Monorepos
Acquaint Softtech is a Python development and IT staff augmentation company based in Ahmedabad, India, with 1,300+ software projects delivered globally across healthcare, FinTech, SaaS, EdTech, and enterprise platforms. Our monorepo engagements follow the architectural framework described in the complete guide to hiring Python developers, and our senior engineers have led polyrepo to monorepo migrations for teams scaling from 5 to 50+ engineers.
Senior Python engineers with monorepo depth. Hands-on with uv workspaces, Pants, GitHub Actions and GitLab CI optimisation, CODEOWNERS enforcement, and dependency graph analysis.
CI/CD performance specialism. Build-time reduction projects using path-based triggers, parallel test execution, per-service Docker imaging, and remote build caching.
Healthcare and FinTech compliance experience. GDPR-compliant monorepo design for BIANALISI, Italy's largest diagnostics group, with audit-grade dependency tracking and per-service deployment isolation.
Transparent pricing from $20/hour. Dedicated Python engineering teams from $3,200/month per engineer. Monorepo audits and migration assessments from $5,000.
To bring senior Python engineers onto your monorepo project quickly, you can hire Python developers with profiles shared in 24 hours and a defined onboarding plan within 48.
The Bottom Line
Python monorepos are not a silver bullet. They are a tool that pays off when applied to the right problem, and that punishes teams who adopt them without the supporting discipline. Pick uv workspaces as the default starting point. Add Pants only when you have measured a problem uv cannot solve. Enforce code ownership from day one. Build per-service Docker images. Run only the tests affected by the change. Migrate steadily, not quickly.
Get those six choices right and the monorepo compounds in your favour. Build times drop. Refactoring across services becomes a routine pull request. New engineers ramp faster because there is one codebase, not seven. Get them wrong and the monorepo amplifies every bad pattern your team already had. The structure does not save you. The discipline that the structure encourages does.
Stuck Between Polyrepo and Monorepo? Or Mid-Migration?
Book a free 30-minute monorepo architecture review. We will look at your current setup, your team size, your service count, and your CI/CD pain, and tell you straight whether monorepo is the right call. No sales pitch. Just senior platform engineers who have done this before.
Frequently Asked Questions
-
When should a Python team move from polyrepo to monorepo?
When the team has at least 10 engineers, 5+ services, and is regularly making changes that span multiple repositories. Below that scale, polyrepo is usually fine. Above it, the cost of cross-repo coordination starts to exceed the cost of adopting monorepo tooling. The clearest signal is when the same change requires synchronised pull requests across 3+ repos and your engineers start dreading those changes.
-
Is uv workspaces enough for a Python monorepo or do I need Pants?
uv workspaces are enough for most teams up to roughly 30-50 engineers and 100 packages. Apache Airflow uses uv successfully with 120 distributions and 3,600+ contributors. You move to Pants when you need affected-only test execution, polyglot support across Python and other languages, or aggressive remote build caching that uv does not yet provide. Start with uv and migrate up only when measurement shows a real bottleneck.
-
How long does a polyrepo to monorepo migration actually take?
For a team with 10 to 30 services, plan for 6 to 12 months across 6 phases: discovery, tooling setup, first small services migration, shared library extraction, remaining services, and decommissioning of old repos. Compressing this timeline reliably produces broken builds and lost git history. Treat it as a multi-quarter program, not a sprint, and budget engineering capacity accordingly.
-
What is the right folder structure for a Python monorepo?
Two top-level distinctions: apps/ for deployable services (each is its own deployment unit) and libs/ for shared internal libraries (imported by apps but never deployed standalone). Add tools/ for internal CLIs, tests/ for cross-package integration tests, and a single pyproject.toml plus uv.lock at the root. The split prevents the most common monorepo failure: shared libraries that accidentally become services because nobody enforced the boundary.
-
How do I prevent dependency hell across a monorepo's many packages?
Use a single uv.lock at the workspace root for deterministic, reproducible builds across all packages. This is the same pattern Cargo uses for Rust and Go modules use for Go. Where libraries genuinely need different version constraints (Team A needs pydantic <2 while Team B needs pydantic >=2), split into separate workspaces inside the same monorepo rather than a single mega-workspace. The single lockfile model breaks down only when version constraints genuinely diverge.
-
Should I use Docker for monorepo CI/CD or run everything natively?
Docker for production deployment, native for most local CI. Per-service Docker images that include only their own dependencies are non-negotiable in production: Doppel reduced 10+ minute deploys to under 4 minutes by stopping the 'install everything for every service' antipattern. For local development and CI test runs, native uv with a shared virtualenv is faster and simpler than spinning up Docker per test job.
-
How do I handle different Python versions across services in a monorepo?
uv handles this natively. Each workspace member can declare its own requires-python in pyproject.toml, and uv installs and manages those Python versions automatically. The monorepo can have one service on Python 3.11 (because of a specific dependency constraint) and another on Python 3.13 without conflict. The uv.python-version file at the root sets the default, and individual packages override when needed.
Table of Contents
Get Started with Acquaint Softtech
- 13+ Years Delivering Software Excellence
- 1300+ Projects Delivered With Precision
- Official Laravel & Laravel News Partner
- Official Statamic Partner
Related Blog
When Is Python Development Too Expensive? Pricing Red Flags That Signal a Bad Vendor
Not all expensive Python development is justified. This guide identifies the exact pricing red flags that signal a bad vendor, with real benchmarks, warning signs, and what fair Python pricing actually looks like in 2026.
Acquaint Softtech
March 26, 2026How to Hire Python Developers Without Getting Burned: A Practical Checklist
Avoid costly hiring mistakes with this practical checklist on how to hire Python developers in 2026. Compare rates, vetting steps, engagement models, red flags, and more.
Acquaint Softtech
March 30, 2026Total Cost of Ownership in Python Development Projects: The Full Financial Picture
The build cost is just the beginning. This guide breaks down the complete TCO of Python development projects across every lifecycle phase, with real benchmarks, a calculation framework, and 2026 data.
Acquaint Softtech
March 23, 2026India (Head Office)
203/204, Shapath-II, Near Silver Leaf Hotel, Opp. Rajpath Club, SG Highway, Ahmedabad-380054, Gujarat
USA
7838 Camino Cielo St, Highland, CA 92346
UK
The Powerhouse, 21 Woodthorpe Road, Ashford, England, TW15 2RP
New Zealand
42 Exler Place, Avondale, Auckland 0600, New Zealand
Canada
141 Skyview Bay NE , Calgary, Alberta, T3N 2K6
Your Project. Our Expertise. Let’s Connect.
Get in touch with our team to discuss your goals and start your journey with vetted developers in 48 hours.