Trunk
Introduction
Repository organization is a foundational decision that affects how teams collaborate, how code is versioned, and how deployable modules are structured.
This article explains the two primary patterns - monorepo and polyrepo - and provides guidance on choosing the right approach.
Monorepo Pattern

A single repository containing more than one deployable module.
Path filters (glob patterns) define module boundaries, allowing independent versioning and deployment despite sharing the same repository.
Characteristics
Single Repository:
- All application code in one repository
- Shared infrastructure and tooling
- Unified version history
- Single source of truth
Code Organization:
- Organized by technology first, then module directories
- Shared tooling and configuration
- Unified dependency management
Example Structure:
monorepo/
├── services/ # module group (e.g., dotnet)
│ ├── api/ # deployable module
│ ├── web/ # deployable module
│ └── worker/ # deployable module
├── shared/ # module group
│ ├── models/ # supporting module (implicit consume)
│ └── utils/ # supporting module (implicit consume)
├── infrastructure/ # IaC (e.g., Bicep)
└── docs/ # documentation (e.g., mkdocs)
Benefits
- Atomic Changes: Refactor multiple services in single commit
- Simplified Dependencies: Shared dependencies at root level, no version conflicts
- Code Sharing: Direct access to shared utilities and libraries
- Unified Tooling: Single CI/CD configuration, consistent build process
- Easier Refactoring: Cross-module changes in one PR
Tradeoffs
- Build Times: Need selective builds and caching strategies
- Access Control: Coarser-grained permissions, all developers see all code
- Repository Size: Can grow large, may need specialized tooling
- Cognitive Overhead: Developers may be overwhelmed by scope
When to Use
- Team-coupled services maintained together
- Heavy code reuse across projects
- Small to medium teams (< 50-100 developers)
- Rapid iteration requiring frequent cross-cutting changes
- Unified ownership of all code
Polyrepo Pattern

One repository per deployable module.
The repository boundary aligns perfectly with the module boundary, making versioning straightforward.
Characteristics
Multiple Repositories:
- One repository per service or component
- Independent version history
- Separate access controls
- Isolated tooling and configuration
Code Organization:
- Each repository is self-contained
- Dependencies managed per repository
- Service-specific configuration
Example Structure:
api-service-repo/
├── .github/ # pipeline
├── api-service/ # deployable module
├── infrastructure/ # IaC
└── docs/ # documentation
shared-models-repo/
├── .github/ # pipeline
├── shared-models/ # deployable module (explicit consume via package)
└── docs/ # documentation
Benefits
- Team Autonomy: Teams own and control their repositories
- Clear Boundaries: Enforces service boundaries, prevents unintended coupling
- Independent Deployment: Services deploy on separate schedules
- Granular Access Control: Fine-grained permissions per repository
- Smaller Repositories: Faster clone times, simpler navigation
Tradeoffs
- Cross-Repository Changes: Multiple PRs needed, coordination required
- Dependency Management: Version conflicts between repositories, contract testing required
- Tooling Duplication: CI/CD configuration duplicated across repos
- Discoverability: Harder to find related code, no unified search
When to Use
- Loosely coupled services with independent lifecycles
- Large organizations with separate team ownership
- Distributed teams with confidentiality needs
- Independent deployment cadences
- Well-defined APIs between services
Repository Types

Poly-repository: One deployable module. Repository boundary = module boundary.
Mono-repository subtypes:
- Team mono: Single team owns multiple modules
- Product mono: Multiple teams collaborate on one product
- Organizational mono: Large-scale (Google/Facebook style, not recommended for most)
Adjacent Repositories
Not all repositories are trunks. Adjacent repositories serve specialized purposes beyond containing deployable modules.
Example: GitOps Repository
A Kubernetes GitOps repository is a special form of IaC constrained to declarations only. It cannot easily be integrated into a trunk, nor should it be: the GitOps repository's history should be the history of the cluster it controls, one-to-one.
Summary
| Repository Type | Purpose | Contains Modules? |
|---|---|---|
| Trunk (mono/poly) | Source of truth for deployable code | Yes |
| GitOps repo | Cluster state declarations | No |
| Config repo | Environment-specific configuration | No |
| Template repo | Repository scaffolding | No |
Adjacent repositories are consumed by or triggered from trunk pipelines, but maintain their own lifecycle and history.
Comparison
| Factor | Monorepo | Polyrepo |
|---|---|---|
| Atomic cross-cutting changes | Excellent | Difficult |
| Traceability | Single timeline | Multiple timelines |
| Team autonomy | Limited | High |
| Pipeline complexity | Requires specialized tooling | Simple |
| Coordinated releases | Simple | Harder |
| Code reuse | Easy (direct consume) | Requires versioning |
| Access control | Coarse-grained | Fine-grained |
| Discoverability | All in one place | Spread across repos |
Anti-Pattern: Technical Boundary Split

Splitting repositories by technical boundary (frontend/, backend/, scripts/, infrastructure/) rather than deployable module boundary creates dependency chaos.
Problems:
- No coherent version for what a release consists of
- Changes to one feature require coordinating multiple PR's and repositories
- No clear deployment boundary
Avoid:
- Separate repos for frontend, backend, scripts, docs, infrastructure unless each is a distinct deployable module with proper contracts
- Loose gatherings of repositories with cross-repository dependencies
- Head-to-head dependencies between repos (use pinning and stitching instead)
Best Practices
Module Boundaries: Define explicit boundaries, document dependencies, enforce with tooling.
Versioning:
- Monorepo: Independent SemVer/CalVer for each releasable module
- Polyrepo: One SemVer/CalVer per repository (can still use implicit for supporting modules)
Dependency Management:
- Root-level lock files for reproducible builds:
| Ecosystem | Lock File | Notes |
|---|---|---|
| Go | go.sum |
Checksums for module dependencies |
| Node.js | package-lock.json |
Or yarn.lock, pnpm-lock.yaml |
| Rust | Cargo.lock |
Exact versions for reproducible builds |
| Ruby | Gemfile.lock |
Resolved gem versions |
| .NET | packages.lock.json |
NuGet package lock (opt-in) |
| Python | poetry.lock |
Or requirements.txt with pinned versions |
| Java | gradle.lockfile |
Or Maven's dependency-lock.json |
Hard dependency rules:
- In-repository bindings are implicit
- Cross-repository bindings are pinned and stitched
- No exceptions: every dependency is either implicit (same repo) or explicitly pinned (external)
Automation:
- Monorepo: Change detection, selective builds tooling (r2r+eac, Nx, Bazel, Turborepo)
- Both: Build, Test, Scans, Verification, Repository templates, shared pipeline definitions, Dependabot
Next Steps
- Unit of Flow - How trunk fits in the 4-component model
- Deployable Modules - What modules trunk contains
- Pipeline - How pipelines work with mono/poly repos
- Environments - Environment types and PLTE
References
Tutorials | How-to Guides | Explanation | Reference
You are here: Explanation — understanding-oriented discussion that clarifies concepts.