Skip to content
ansezz.
← Back to blog
Architecture Feb 22, 2026 6 min read 1,149 words

From monolith to micro-services: a senior dev's guide to pragmatic scaling

Skip the big-bang rewrite. The strangler fig pattern, anti-corruption layers, Docker-first migration, and GKE/Coolify operations — how I peel services off a Laravel monolith one endpoint at a time without breaking revenue.

Anass Ez-zouaine

Backend · Architect · AI

▸ Share

Pop-art illustration of a monolith being gradually replaced by smaller services

Your monolith is a ticking time bomb and every feature you add makes the explosion more inevitable.

I have seen it happen a dozen times. A startup begins with a clean Laravel or Rails app. It is fast. It is easy. It is productive. Then the team grows. The code base swells. Suddenly, a simple change to the checkout logic breaks the authentication system. Deployments that used to take five minutes now take forty. You are not scaling your business anymore — you are managing technical debt.

This is the point where most developers start dreaming of micro-services. They imagine a world where every service is isolated and deployments are instant. But the reality is often a nightmare. If you do it wrong, you end up with a distributed monolith. You get all the complexity of networking with none of the benefits of isolation.

The solution is not a “big bang” rewrite. It is pragmatic scaling. I use the strangler fig pattern to move from monoliths to micro-services without losing my mind or my job.

The problem with the big bang

Strangler fig pattern diagram — new services wrap the legacy monolith

When a monolith becomes too heavy, the immediate reaction is to want to scrap it. I have seen companies spend two years on a rewrite only to ship a product that has half the features of the original. The business dies while the engineers play with new toys.

The monolith is not your enemy. It is just a phase. The real problem is coupling. When every part of your app knows too much about every other part, you cannot move. You are stuck in a web of dependencies. If you try to jump straight into micro-services, you will likely just port those dependencies into a network layer. Now, instead of a function call failing, you have a 500 error across a network socket.

I prefer a slower, more deliberate approach. I focus on high-value extractions. I look for the parts of the app that hurt the most. Is the image processing service slowing down the web server? Is the reporting engine locking up the database? Those are your first candidates for micro-services.

The strangler fig pattern in practice

I named this approach after a tree that grows around another tree. It starts as a small vine and eventually replaces the host entirely. In software, this means building new features as services while the old monolith remains.

The process starts with an API gateway or a load balancer. I use Nginx or Cloud Armor on Google Cloud to route traffic. If a request comes for /api/v1/orders, it goes to the new service. Everything else goes to the old monolith.

This allows me to test the new service in production with real traffic while the monolith acts as a safety net. If the new service fails, I just flip the routing back. I do not have to migrate everything at once. I can migrate one endpoint at a time.

Containerization with Docker

Annotated Dockerfile snippet for a Laravel micro-service

You cannot do micro-services without Docker. I treat every service as a black box. The monolith might be running on an old version of PHP, while the new service is a lean Go binary or a modern Laravel instance. Docker makes this possible.

I start by containerizing the monolith. Even if it stays as a monolith for another year, putting it in a container forces me to define its environment. It makes the infrastructure reproducible.

# a simplified example of a service container
FROM php:8.3-fpm

WORKDIR /app
COPY . /app

RUN apt-get update && apt-get install -y \
    libpq-dev \
    && docker-php-ext-install pdo_pgsql

EXPOSE 9000
CMD ["php-fpm"]

Once the monolith is containerized, I can deploy it to a platform like Google Kubernetes Engine (GKE). This is where the real power of micro-services comes in. I can scale the order service to fifty instances during a sale while keeping the blog service at two.

Communication and the anti-corruption layer

Routing diagram showing API gateway dispatching between monolith and new services

The hardest part of micro-services is not the code. It is the data. Your monolith has a single database. Your micro-services should each have their own. But how do they talk?

I use an anti-corruption layer (ACL). When I extract a service, I do not let it reach back into the monolith’s database. That would be cheating. Instead, I create an interface. If the new service needs user data, it asks the monolith via a private API or a message queue like Google Pub/Sub.

This keeps the new service clean. It does not care about the messy database schema of the legacy app. It only cares about the data it receives through the ACL. Eventually, when the user logic is also migrated, I just update the ACL to point to the new user service.

Cloud infrastructure and DevOps

Cloud infrastructure overview — GKE, Pub/Sub, managed databases

Scaling a monolith usually means buying a bigger server. Scaling micro-services means managing a fleet. I rely heavily on cloud-native tools to manage the complexity.

I use Terraform to manage my infrastructure as code. This ensures that my staging and production environments are identical. If I need a new database for a service, I define it in code. I do not click around in a dashboard.

On the DevOps side, I use tools like GitHub Actions or Coolify for deployments. Every service has its own pipeline. If I update the checkout service, I only deploy the checkout service. I do not have to worry about the rest of the system.

The hidden costs of micro-services

I would be lying if I said this was all sunshine and rainbows. Micro-services come with a “complexity tax.” You now have to deal with distributed logging, service discovery, and eventual consistency.

I tell my clients that they should only move to micro-services when the pain of the monolith is greater than the cost of the complexity tax. If your team is three people and your app is simple, stay in the monolith. You will move faster.

But if you are hitting walls every day and your developers are afraid to touch the code, it is time to start strangling.

Pragmatic takeaways for your next move

  • Start with an API gateway to handle routing.
  • Containerize your monolith first to normalize the environment.
  • Use the strangler fig pattern to migrate one domain at a time.
  • Build an anti-corruption layer to keep new services clean.
  • Invest in infrastructure as code early on.
  • Only split when the monolith starts to hurt your productivity.

Migration is a marathon, not a sprint. I have spent months on a single extraction just to make sure it was perfect. The goal is not to have micro-services. The goal is to have a system that can grow with your business.

Have you ever tried a “big bang” rewrite only to regret it six months later? Tell me about it — I collect these stories for a reason.

▸ Made it to the end? Send it around.

▸ Share