A rugged case open on a worn wooden workbench, its foam insert cut to the exact shape of the machined part inside. It is like a repository shaped to match how the software ships.

The Repository Should Match the Way the Software Ships10fd32f

By

On this page

A five-line change should not take five days to ship, but anyone who has chased a small fix across four repositories, waiting for pull requests to land in the right order, knows exactly how it happens. The code is the fast part, and the coordination is what burns the week.

Most arguments about monorepos start in the wrong place, usually with tooling: build speed, dependency management, and whether Google does it. The question underneath all of them is coordination.

Monorepos solve a problem and granular repos create one, and both halves need saying up front, because the lazy version of this conversation turns into another round of engineering tribalism. One camp acts like putting everything in one repository magically fixes software delivery, and the other acts like smaller repositories automatically produce cleaner systems. Neither camp is right.

A repository structure is a delivery model. It says something about ownership, release flow, and how much coordination people need before they can safely ship, and when we forget that, we end up arguing about folder structure while the system breaks somewhere else.

We Have Been Here Before

It is worth being honest about where we came from. In the CVS, Visual SourceSafe, and SVN days, most repositories were monorepos in everything but name, and before that plenty of systems lived in big folder structures on shared drives, with code, scripts, and install notes piled in next to whatever else the team needed to keep the system alive. Nobody called them monorepos because there was nothing special to call them. That was just where the code lived.

Releases were painful because everything was tied together. Builds were fragile, ownership was often unclear, and a change in one area could break some unrelated part of the system, usually discovered by the person trying to ship. In a lot of places waterfall release cycles were less a philosophy than the only sane-looking option when the whole system moved as one large, risky object. You bundled everything up, scheduled the deployment window, and pulled out rollback instructions that may or may not have been tested recently. Then everyone held their breath.

It was not elegant, but it made a certain kind of sense, since the release process had grown into a ceremony around all that tangled risk. Breaking systems into smaller repositories made sense too. Smaller repos felt like progress because in many ways they were progress, giving teams clearer boundaries, lighter local builds, and at least the theory that they could ship smaller pieces independently. Teams could finally stop treating the entire software estate as one release object.

The coordination problem did not go away, though. It moved.

Granular Repos Hide Coupling

Lots of small repositories can make a system look cleaner than it is. You may have one repo for the frontend, another for the API, another for a shared client library, a couple more for infrastructure and database migrations, and some internal tool nobody remembers until it breaks. On paper that looks like separation.

In practice, a single feature may need changes in five of those repositories, and they have to land in the right order. The library has to be published before the service can consume it, and the service has to be deployed before the frontend can rely on the new behavior. Meanwhile the database migration has to work with both the old and the new code, and the infrastructure change has to land before the service starts using the new dependency. All of that coupling is still there, spread across package versions, pipeline timing, and Slack threads where nobody can see it at once.

So instead of one large codebase with visible coupling, you get many smaller ones with hidden coupling, and instead of one painful release train you get version drift, stale contracts, and a stack of pull requests across repositories. The diff might be five lines, and the week goes to landing those pull requests in the right order.

Right about here, monorepos start looking attractive again, and with good reason. A monorepo can make cross-cutting changes easier, cut down on dependency pinball, and allow larger refactors that would be nearly impossible across many repositories. Seeing coupling is a long way from fixing it, but a monorepo does at least put it in front of you, and it tends to reveal that systems are more connected than the org chart wants to admit.

The Monorepo Failure Mode

The failure mode I worry about with monorepos is the one-size-fits-all effect. At first, standardization sounds like the whole point: one repository, one pipeline, one way to deploy. It is easy to sell because the alternative looks messy, and nobody wants seventy repositories with seventy different deployment stories.

Systems push back, because different things have different lifecycles. A web application, a Terraform module, and a mobile app do not behave the same way just because they share a root folder. They have different runtime requirements, different rollback paths, and different release approvals, and often a different team carrying the pager after the code ships.

The monorepo starts growing exceptions, and they follow a pattern. Everything deploys through the same pipeline, except for that package. Everything runs the same test workflow, except for the legacy module that takes forty minutes. Everything follows the standard process, except for the one thing everyone is afraid to touch. From a distance the process still looks standardized, but up close it is a pile of conditional logic and tribal knowledge, which is complexity with a company-approved label on it.

This gets dangerous once CI/CD enters the picture, because CI/CD turns repository structure into daily behavior. Saying code can live together is easy. Proving that teams can build, deploy, and roll back their work without stepping on each other is harder. A deployment pipeline is an organizational contract that defines what has to be true before code is allowed to move forward, and when everything lives in one repository, that contract has to serve many kinds of work at once. If it is too loose it fails to protect production, and if it is too strict it slows down teams that should be able to move independently. Add enough exceptions and nobody understands the contract anymore, which is usually when the people problems start.

Monorepos Have a People Problem

Engineers like to talk about monorepos as if the problem were mostly mechanical, solvable with a better build graph, smarter test selection, and better caching. All of that helps, and none of it touches the human problem. When a monorepo build breaks, who owns it? When one team's flaky test blocks another team's release, who feels the pain, and who has to prove the downstream systems still work after a shared package changes? When a team asks for a pipeline exception, who decides whether it is justified or whether the system is quietly becoming unmaintainable? Once every team shares the same repository, the social contract matters as much as the technical one.

Monorepos can get stressful here because they compress a lot of organizational behavior into one visible place. Teams that were already aligned tend to benefit. Teams that were not may discover that their boundaries were more aspirational than anyone admitted, and that discovery is useful, even though it is not free. A monorepo will reveal missing ownership faster, and it has no way to create ownership that was never there.

So I do not like treating a monorepo as an architecture strategy by itself. I think of it as a forcing function. It can force shared standards, dependency visibility, and build discipline, and it can make teams confront coupling they had been ignoring. What it cannot do is decide what should be independently deployable, who owns production, or whether a service should be allowed to release without coordinating with half the company. Those are architecture and ownership decisions, and the repository should reflect them once they are made.

Size Repositories Around Deployables and Publishables

My default preference is simple. Size repositories at the level of a deployable or a publishable. That does not mean every Lambda function or small service needs its own repository, because that way lies madness. Too many tiny repos create their own tax, turning simple changes into dependency scavenger hunts and every improvement into a multi-step release campaign.

In my own work that default comes out as polyrepos. I don't like monorepos for the systems I build, because in my experience they turn messy and unwieldy, so each deployable gets its own repository. Infrastructure follows the same rule. Shared infrastructure lives in its own repo, while the infrastructure that belongs to one thing, an API or a batch job, lives in that thing's repo next to its code, where it ships through the same pipeline as the code that depends on it.

What I want is for the boundary to line up with the way the software moves through the world. If something deploys independently, gets published as a versioned package, or carries its own release lifecycle and rollback path, that is a boundary worth drawing, and the repository should make it obvious instead of burying it inside a larger structure because the organization wanted fewer repos to look at.

In practice, a backend service with its own deployment pipeline probably deserves a repository boundary, and a shared library published and consumed by multiple systems may deserve one too. An application made of internal modules that always build, test, and deploy together can live happily in one repo, and so can a cluster of tightly coupled packages released as a unit. What matters in each case is that the repository boundary tells the truth.

When the repo boundary matches the delivery boundary, CI/CD gets much easier to reason about. The pipeline validates the thing that will ship, the release belongs to the team responsible for the outcome, and a broken build has an obvious owner. Boring production is a feature, and it starts with boundaries like these, where rolling back your service never means rolling back somebody else's code.

Standardization Should Not Mean Pretending Everything Is the Same

None of this is an argument against standardization. Most organizations need more consistency, in the form of common pipeline patterns, security checks, and rollback discipline. Common still does not have to mean identical.

A good platform gives teams paved roads. It makes the right thing easy and the risky thing visible, and it reduces unnecessary variation without pretending that a frontend app, an API, and a shared library all share a lifecycle. Bad standardization insists everything work the same way, while the useful kind keeps the important parts consistent and makes the differences explicit, and most of the job is knowing which parts are which.

So if a service needs a special deployment path, make it visible. If a package has different versioning rules, document and automate them. And if a legacy module has a slow test suite, resist the urge to hide it under a pipeline exception forever, and decide instead whether the cost is acceptable, who owns it, and when it gets fixed.

The exceptions themselves are fine. The trouble starts when they are hidden, and that happens in monorepos and many-repo systems alike, because in both the actual process gets buried. In a many-repo world it hides in dependency chains and release coordination, and in a monorepo it hides in conditional pipeline logic and rules nobody wrote down.

The Better Question

The better question is where the coordination pain should live, since every repository strategy has a cost. A monorepo centralizes coordination. It can make dependencies visible and enforce shared standards, and it can also produce pipeline complexity, noisy builds, and pressure to make unlike things behave alike. Many repos push the coordination outward, which gives teams autonomy and focused pipelines while hiding coupling and making cross-cutting changes painful.

Nobody gets a free answer here. The right one matches the system you have and the system you are disciplined enough to operate, and teams tend to skip that second half. Picking a monorepo to feel like a big engineering organization is a bad reason, and so is picking many repos so every team can feel autonomous. Pick the structure that reflects how ownership and delivery work in your organization today.

If teams release together, share a lifecycle, and need frequent cross-cutting changes, a monorepo may be the right call. If teams own independent deployables with different runtimes and release paths, forcing them into one repository tends to create more confusion than clarity. A versioned, published library has a lifecycle of its own, and so does a service that deploys and rolls back by itself, and the repository structure should say so.

The Takeaway

Monorepos and granular repos are both tools for managing coordination, and both fail when a team uses them to avoid the harder conversation about ownership and delivery. The current monorepo trend is a reaction to what the many-repo era did to small changes, and it fixes part of that, while staying perfectly capable of recreating the old shared-drive release pain with better tooling.

When a monorepo tries to make everything one size fits all, the organization pays in process stress, and CI/CD is where the bill shows up, because pipelines are where architecture and ownership stop being opinions and start becoming facts. My bias is to size repositories around deployables and publishables. It is an imperfect rule and a useful default, because it keeps the repo boundary close to the delivery boundary and ownership close to production.

A monorepo can be the right answer when it reflects a shared lifecycle, and it gets dangerous when it forces unrelated lifecycles to pretend they are the same. Either way, what you are after is a delivery system with honest boundaries, where a five-line fix reaches production without a week of lining up pull requests in four different repositories.

Frequently asked questions

Is a monorepo better than many small repositories?

Neither is better by default. Both are tools for managing coordination, and both can fail when they are used to avoid harder conversations about ownership and delivery. A monorepo centralizes coordination and makes coupling visible; many repos decentralize it and can hide coupling. The right answer is the one that matches the system you actually have and are disciplined enough to operate.

Why isn't repository structure mainly a tooling question?

Because a repository structure is a delivery model. It encodes ownership, dependencies, testing, release flow, deployment risk, and how much coordination people need before they can safely ship. Build speed and dependency management matter, but the question underneath is coordination, and arguing only about tooling means arguing about folder structure while the system breaks somewhere else.

How should I decide where to draw a repository boundary?

Size repositories around deployables and publishables. If something deploys independently, is published as a versioned package, or has its own release lifecycle, rollback path, and operational owner, that usually deserves a repository boundary. The goal is to make the repo boundary match the delivery boundary so the pipeline validates the thing that actually ships.

What is the main failure mode of a monorepo?

The one-size-fits-all effect. When unlike lifecycles (a web app, a service, a shared library, an infrastructure module) are forced through one pipeline, the process slowly fills with exceptions and tribal knowledge. It still looks standardized from a distance, but up close it becomes conditional logic nobody fully understands. That is complexity with a company-approved label.

Does a monorepo fix ownership problems?

No. A monorepo is a forcing function. It can force shared standards, dependency visibility, and build discipline, and it will reveal a lack of ownership faster. It cannot create ownership where none exists, decide what should be independently deployable, or decide who owns production. Those remain architecture and ownership decisions.

Should standardization mean every team works the same way?

No. Good standardization makes the important parts consistent, things like pipeline patterns, quality gates, security checks, and rollback discipline, while making the differences explicit. Visible exceptions are fine, and hidden ones cause the trouble. Monorepos and many-repo systems both go wrong when the actual process gets buried, whether in dependency chains or in conditional pipeline logic.

Conversation

    Log in to join the conversation.

    © 2026 ABWaters. Thinking out loud.