A few years ago, I watched a three-person team spend three months migrating a working Node.js monolith into several microservices. They had around 400 monthly active users. By the end of it, deployments took longer, debugging had become an archaeological dig across log aggregators, and shipping a feature that used to take an afternoon now took a week of cross-service coordination.
They shipped less. They paid more. They were proud of the architecture.
This is the trap. Microservices have become the default "serious" architecture in a lot of developer circles, copied from blog posts written by engineers at Netflix, Uber, and Amazon, then dropped directly onto products that have nothing in common with those companies. If you're a solo dev or a small team, I'm going to be blunt: you almost certainly don't need them, and reaching for them early is one of the most expensive mistakes you can make.
The blog posts you're reading were written for a different problem
When Netflix talks about microservices, they're solving for thousands of engineers working in parallel, hundreds of millions of users, and the cost of a single bad deploy taking down a global streaming service. Uber's architecture exists because they have specialized teams that need to ship independently without coordinating with everyone else. Amazon famously broke things apart because they hit organizational scaling limits, not technical ones.
None of that is your problem. Your problem is shipping a feature this week, fixing a bug your three users reported, and keeping the hosting bill under what you charge in MRR. The architectural pattern that solves "500 engineers can't merge to main without conflicts" is not the same pattern that solves "I want to add Stripe checkout by Friday".
What you actually pay for
Microservices aren't free. The price tag isn't on the AWS bill (though that's part of it) it's hidden in places that don't show up until you're already committed:
-
Operational complexity: Every service needs its own deployment pipeline, its own monitoring, its own logging, its own error tracking, its own runbook. Multiply that by seven services and you've just signed up for a part-time DevOps job you didn't have before. Hope you like writing Terraform.
-
Network failure modes: A function call that used to be a single in-process invocation is now an HTTP request that can timeout, return a 503, get partially completed, hit a retry storm, or fail because someone's IAM role expired. Distributed systems introduce entire categories of bugs that don't exist in a monolith, and most small teams have no idea how to debug them.
-
Data consistency: As soon as your data lives in separate databases, a single operation can no longer be one clean transaction that either fully succeeds or fully fails. Now you're stitching state together across services, handling the half-finished cases, and hoping nothing drifts out of sync. Your users don't care about any of that. They care that their order shows up.
-
Cognitive load: A monolith you can hold in your head. Seven services with their own repos, dependencies, versions, and deploy schedules, you cannot. Every new feature now requires you to figure out which service it belongs in, whether it crosses boundaries, and how to test the integration. That's all time you're not spending on the actual product.
-
Deployment velocity: Which is the cruel irony of the whole thing. People adopt microservices to ship faster. Small teams almost always end up shipping slower, because every cross-cutting change now requires coordinated deploys across multiple repos.
"But what about scaling?"
Here's the thing about scaling: you don't have a scaling problem. I promise. If you're reading this post, you do not have a scaling problem. A boring Postgres database on a $40/month VPS will comfortably handle more traffic than 99% of indie products will ever see. A modern Hetzner box with a few cores and decent RAM can serve millions of requests a day if you don't do anything stupid.
Premature optimization at the architectural level is the most expensive kind of premature optimization, because you can't easily back out of it.
If you do hit a real bottleneck (and you'll know, because something will be on fire) you can extract one service from your monolith at that point. That's the move. You don't need to start there.
When microservices actually make sense
I'm not saying microservices are bad. I'm saying they're a tool that solves specific problems, and those problems are rarely yours. The legitimate reasons to reach for them are mostly organizational, not technical. For example:
-
Multiple teams that need to ship independently and are stepping on each other in a shared codebase. This is real, but you probably have one team, or you are the team.
-
Genuinely different scaling profiles. One part of your system is CPU-bound, another needs GPU access, another has wildly different traffic patterns. Splitting them can make sense. But "the auth flow and the dashboard have different scaling needs" is usually not actually true at your traffic level.
-
Regulatory or security isolation requirements that force a hard boundary (payment processing in a separate, audited environment, for example). Fine. That's one service, not seven.
-
Polyglot needs that a monolith can't accommodate. Maybe one piece really does need to be in Go or Rust for performance reasons. But most of the time, it doesn't.
Notice none of these is "because microservices are the modern way to build software".
What to build instead
A boring, well-organized monolith. Seriously. Here's what I'd actually reach for as a small team or solo dev:
A single codebase, ideally in a language you know well. One database, probably Postgres. A modular structure inside the code (directories or modules organized by domain, with clear boundaries) so that if you ever do need to extract a service, the seams are already there. This is sometimes called a "modular monolith," and it gives you most of the architectural benefits of microservices with none of the distributed-systems pain.
Deploy it to a single host until it hurts. Run a background job queue in the same process or as one sibling worker. Cache with Redis if you need to. Use a CDN for static assets. That's the whole stack. You can run a real business on this for years.
When something genuinely outgrows the monolith (a specific endpoint is eating all your CPU, or you genuinely need to scale one workload independently) extract that one piece. One service. Not seven. Not preemptively. Reactively, in response to actual pain you've measured.
The honest reason people do it anyway
I'll close with the uncomfortable part. A lot of the time, microservices get adopted not because the team has a technical problem, but because the team has a resume problem, or a status problem. "I built a monolith" doesn't impress recruiters. "I architected a distributed system on Kubernetes with Kafka and a service mesh" does, even if the product behind it serves three hundred users.
If you're optimizing your architecture for your next job interview rather than for your current product, that's a choice you can make. Just be honest with yourself about it. Don't pretend it's about scale.