DHH Says You Don't Need Microservices. Here Is Why He Ships Monoliths
David Heinemeier Hansson has been publicly and repeatedly wrong according to a large segment of the software industry. He created Ruby on Rails, co-founded Basecamp (now 37signals), and has spent the last several years making the case that the entire trajectory of backend architecture has drifted into unnecessary complexity.
His argument is not that microservices are bad. It is that most teams using microservices do not need them, would be faster and more reliable without them, and adopted them because the industry made it socially costly to admit you are running a monolith.
This is not a fringe position held by someone who has not seen scale. 37signals runs Basecamp and HEY — applications used by hundreds of thousands of paying customers — on what DHH calls "the majestic monolith." When they migrated HEY's email processing away from cloud infrastructure to on-premise hardware, they found they could handle the same load at dramatically lower cost and with meaningfully less operational complexity.
The Microservices Premise Most Developers Accept Without Examining
The conventional wisdom: monoliths are what you build when you are small. As you grow, the monolith becomes unmanageable — too much code, too much team coordination, too tightly coupled to scale. The natural path leads to breaking services apart, each deployed independently, each scalable on its own.
This narrative is so pervasive that it has become a hiring signal. Saying you run a monolith in a technical interview at certain companies carries an implied apology.
The problem: this narrative conflates several distinct problems.
Deployment coupling — the inability to deploy one part of the system without deploying the whole thing. This is a real problem in some monoliths. It is not an inherent property of monolithic architecture.
Scaling constraints — the inability to scale high-traffic components independently. Real, but relevant only when specific components have significantly different load profiles.
Team coordination — the cost of multiple teams working on the same codebase simultaneously. Real, but often better solved through modular code structure than through architectural separation.
Each of these problems can be solved within a monolith. The question is whether the solution involves decomposing into separate services — which introduces its own category of problems — or whether there are simpler solutions that don't require distributed systems.
The Costs of Microservices That Teams Undercount
When a team decomposes a monolith into microservices, they solve the three problems above — but they inherit a new set of problems:
Network failure is now application logic. Function calls don't fail. Network calls do. Every service boundary in a microservices architecture requires handling network timeouts, retries, circuit breakers, and partial failures. This is not accidental complexity — it is inherent to distributed systems.
Distributed transactions are hard. A monolith can wrap multiple database operations in a single transaction. Microservices operating on separate databases need saga patterns, eventual consistency, or distributed transaction protocols — all of which are significantly more complex than local transactions.
Observability requires more infrastructure. Debugging a monolith: you have one log file, one call stack, one process. Debugging microservices: you need distributed tracing (Jaeger, Zipkin, OpenTelemetry), correlation IDs that propagate across service boundaries, and tooling that can reconstruct a request's path across multiple services.
Deployment becomes an orchestration problem. Deploying a monolith: you deploy one thing. Deploying microservices: you manage service dependency ordering, rolling updates, version compatibility between services, and service discovery.
DHH's point is not that these problems are unsolvable. It is that teams adopt microservices to solve deployment coupling, scaling constraints, and team coordination — then spend the next two years solving distributed systems problems that didn't exist before.

What the Majestic Monolith Actually Looks Like
The "majestic monolith" isn't a tangled ball of mud. It's a well-structured codebase that happens to deploy as a single unit, organized with the same care you'd put into services but without the distributed systems overhead.
The key characteristics:
- Strong module boundaries that prevent the codebase from becoming entangled. Different domains (billing, notifications, analytics) live in separate modules with explicit interfaces, even if they share the same process.
- Single deployment unit that simplifies operations. One thing to deploy, one thing to monitor, one thing to debug.
- Direct function calls between modules rather than network calls. Transactions remain local. Failure modes stay simple.
- Database access that can span modules without network overhead. A query that joins billing data with user data is a single SQL query, not an API call followed by another API call.
37signals' production monolith for Basecamp: hundreds of thousands of users, complex features (email, documents, chat, project management), deployed as a Rails monolith. Not because they couldn't decompose it, but because they actively chose not to.
When Microservices Earn the Complexity
DHH's position isn't that microservices are never appropriate. It's that they're appropriate less often than the industry assumes.
Microservices earn the complexity when:
Teams are genuinely independent and deploy on different cycles. If two teams need to deploy independently without coordinating, services provide that isolation. But this requires teams at a scale where the coordination cost of shared deployment exceeds the coordination cost of service boundaries.
Components have dramatically different scaling profiles. If your image processing pipeline needs 100x the compute of your web frontend during peak load, independent scaling makes sense. But if all parts of the application scale proportionally, unified scaling is simpler.
Technology heterogeneity is genuinely required. If one component truly needs a different runtime (a machine learning inference service needs Python/TensorFlow), services provide the isolation. But adopting services just to use different languages for different domains adds complexity without benefit.
The organization is large enough to absorb distributed systems overhead. At Amazon and Netflix scale — thousands of engineers, thousands of services — the investment in distributed systems tooling amortizes across the entire organization. At a 20-person startup, it doesn't.
The Hidden Argument: Operational Cost
When 37signals moved HEY's email processing from cloud to on-premise hardware, the numbers were striking. The operational cost per unit dropped substantially, and the performance improved because the hardware was dedicated rather than shared with other cloud customers.
This is the argument that doesn't get made in architecture discussions: the operational cost of microservices isn't just the code. It's the Kubernetes cluster, the service mesh, the distributed tracing infrastructure, the API gateway, the service discovery system, and the on-call burden that comes with operating all of it.
DHH's calculation: for a team of 50 engineers, the savings from not operating that infrastructure is significant. Those engineering hours go into product work instead.
This isn't anti-technology. It's a cost-benefit analysis that includes operational costs that get handwaved away when microservices get adopted.
The Modular Monolith as Middle Ground
Between the "everything in one file" monolith and the full microservices architecture, there's a middle path that addresses the real problems without the distributed systems overhead:
The modular monolith organizes code into well-defined modules with explicit interfaces, shared database, and single deployment. It solves team coordination through module boundaries and ownership. It solves deployment coupling through disciplined module isolation that allows individual modules to be deployed or rolled back. It solves most scaling concerns through horizontal scaling of the entire application.
The modular monolith is what most teams actually want when they say they want microservices. The distributed deployment is often what they don't actually need.

What to Actually Consider Before Decomposing
The questions to answer before adopting microservices:
-
What specific problem are you solving? Identify the exact pain point: deployment coupling, team coordination, scaling. Make sure the pain is real and significant, not hypothetical.
-
Can the problem be solved within the monolith? Module boundaries, feature flags, and disciplined code organization solve many of the problems attributed to monolith architecture without distributed systems overhead.
-
What's the team size and scale? Microservices make economic sense at team sizes and traffic volumes large enough to amortize the distributed systems overhead. For most teams, that threshold is higher than they assume.
-
Who will operate the distributed infrastructure? Someone needs to manage Kubernetes, service mesh, distributed tracing, and API gateway. If that's the same engineers writing product features, the tradeoff may not be worth it.
-
What's your actual deployment frequency problem? If you're deploying once a day and it takes 10 minutes, the problem is probably your deployment pipeline, not your monolith.
DHH's position, applied practically: default to the monolith. Add services when you have specific, documented evidence that the monolith is the bottleneck — not when the industry hype cycle makes it feel socially necessary.
The teams that build the most shipping velocity are often the ones that choose boring, simple architectures deliberately. The architectural sophistication that matters for users is in the product features, not in the service topology.





