---
title: "Migrating a Monolith to Kubernetes Without a Big-Bang Cutover"
lang: "en"
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/post/monolith-to-kubernetes-strangler-migration
---

![Blog post image for Migrating a Monolith to Kubernetes Without a Big-Bang Cutover - Using the strangler-fig pattern to move a large monolith onto EKS service by service, with a routing facade, gradual traffic shifting, and a rollback at every step.](/_astro/hero.CAKh7bXG_Z1PQJUo.webp)

[Home](/)›[Case studies](/case-studies)›[All Categories](/case-studies/categories)›[DevOps](/case-studies/categories/devops)

Case studies

[Prev in DevOpsBuilding an Internal Developer Platform on Backstage and GitOps](/case-studies/post/internal-developer-platform-backstage-gitops)[Next in DevOpsQuenchWorks: Building a 0-CVE Container Image and Helm Chart Catalog](/case-studies/post/quenchworks-zero-cve-catalog)

[DevOps](/case-studies/categories/devops)[Cloud Native](/case-studies/categories/cloud-native)[Architecture](/case-studies/categories/architecture)

# Migrating a Monolith to Kubernetes Without a Big-Bang Cutover

[Mohammad Abu Mattar](/authors/mohammad-abu-mattar)Published: 20 Jul 2026Updated: 20 Jul 202607 Mins read11 Mins listen

[Markdown for AI(opens in a new tab)](/post/monolith-to-kubernetes-strangler-migration/index.md "Open the plain-Markdown version of this page, for pasting into an AI tool")

TL;DR

Using the strangler-fig pattern to move a large monolith onto EKS service by service, with a routing facade, gradual traffic shifting, and a rollback at every step.

Series

[Containers & Kubernetes](/series/containers--kubernetes)1/1

All posts in this series (1)

Case Studies1

1.  [Migrating a Monolith to Kubernetes Without a Big-Bang CutoverYou are here](/case-studies/post/monolith-to-kubernetes-strangler-migration)

### Migrating a Monolith to Kubernetes Without a Big-Bang Cutover

Contents

[Impact](#impact)[The problem](#the-problem)[Constraints](#constraints)[Architecture](#architecture)[The routing facade](#the-routing-facade)[Implementation](#implementation)[Results](#results)[Lessons](#lessons)[Frequently Asked Questions](#frequently-asked-questions)[References](#references)

Almost every failed “let’s move off the monolith” project shares one detail: the plan was a big-bang cutover. Rewrite in parallel, pick a weekend, flip the switch, and pray. This is the opposite of that. A large application moved onto EKS one service at a time using the strangler-fig pattern, with a routing facade in front, traffic shifting gradually per route, and a working rollback at every single step. The team kept shipping features throughout, and at no point was the whole application in the air.

## [Impact](#impact)

The application reached Kubernetes with no big-bang moment. Every route moved gradually behind a facade with a working rollback, so no single step was ever high stakes and delivery never froze.

The gains were structural rather than a one-time event. Each extracted service got independent deploys and its own scaling, so teams stopped blocking each other and the hot paths no longer forced the whole application to scale with them.

0big-bang cutover events

0feature freezes

0%of traffic shifted route by route

## [The problem](#the-problem)

The monolith itself was not the enemy. It ran fine, the team knew it, and it paid the bills. The problem was that it had become the bottleneck for everything else. Deploys were all-or-nothing, so one risky change held up every other team’s work. Scaling meant scaling the entire application even when only one part was hot. And onboarding a new engineer meant handing them the whole thing at once.

The tempting fix, a full rewrite with a cutover, is where teams get hurt. You freeze features to build the replacement, the replacement drifts from the original as the original keeps changing, and the cutover becomes a single high-stakes event with no safe rollback. If anything goes wrong at 2am on migration night, the only option is a panicked revert of everything.

The goal was to get the benefits of independent services without ever betting the business on one cutover. That means the old and new systems have to run side by side, in production, for as long as it takes.

## [Constraints](#constraints)

-   **No big-bang cutover.** At no point could correctness depend on a single switch-flip.
-   **No feature freeze.** The monolith kept shipping features throughout the migration.
-   **A rollback at every step.** Each increment had to be revertible in minutes, not hours.
-   **No shared-database free-for-all.** Extracted services own their data; the goal was decoupling, not a distributed monolith on one schema.
-   **Prove parity before deleting anything.** Old code stayed until the new path was verified against it.

## [Architecture](#architecture)

Before the migration, the shape was familiar: an Application Load Balancer in front of a monolith running across an Auto Scaling group, all talking to one shared relational database.

An ALB in front of the monolith on an Auto Scaling group, backed by a single shared RDS database.

The target keeps the monolith running, containerized, inside an EKS cluster, and puts a routing facade in front of everything. The facade is the heart of the pattern. It looks at each request and decides whether that path has been migrated to a new service or still belongs to the monolith. Extracted services get their own data stores; the monolith keeps its shared database until its remaining parts are small.

The facade routes migrated paths (users, billing) to extracted services with their own databases, while everything else still goes to the containerized monolith.

The name comes from the strangler fig, a plant that grows around a tree and gradually replaces it. The new system grows around the monolith, taking over one responsibility at a time, until the original is either gone or small enough to leave alone. Nothing about it requires a dramatic finish.

## [The routing facade](#the-routing-facade)

The facade is where the safety comes from. Every request enters through it, and a route table decides the destination. A path that has been migrated goes to the new service; everything else defaults to the monolith. Migration of a single route is itself gradual, too: you shift a small percentage of that route’s traffic to the new service, watch it, and widen only when it holds. If the new service misbehaves, the facade falls straight back to the monolith, which is still running and still correct.

The facade sends migrated paths to the extracted service and everything else to the monolith, shifting each route gradually with instant fallback.

In practice the facade can be an ingress with weighted routing, an API gateway, or a service mesh. The mechanism matters less than the property: per-path routing plus per-path traffic weight plus instant fallback.

facade-route.yaml (illustrative weighted routing)

```
1# /users is being migrated: 10% to the new service, 90% still to the monolith.2http:3  - match:4      - uri:5          prefix: /users6    route:7      - destination: {host: users-service}8        weight: 109      - destination: {host: monolith}10        weight: 9011  - route: # default: everything else stays on the monolith12      - destination: {host: monolith}13        weight: 100
```

## [Implementation](#implementation)

The migration ran as a loop, not a project plan with an end date. Each pass picked one seam, extracted it, shifted traffic, verified, and cleaned up.

Pick a loosely-coupled seam with a clear data owner, build it with its own store, route to it gradually, verify parity, remove it from the monolith, and repeat until the monolith is small enough.

1.  **Containerize the monolith first.** Before extracting anything, get the monolith itself running in EKS behind the facade. Now old and new live in the same place, and the facade is the only thing in front.
    
2.  **Pick a loosely-coupled seam.** Choose a capability with a clear boundary and a data set it mostly owns, for example users or billing. Avoid the tangled core on the first pass; early wins build trust.
    
3.  **Build the service with its own data.** Give the extracted service its own database rather than pointing it at the monolith’s schema. Backfill and keep it in sync during the transition, but the target is independent ownership.
    
4.  **Route to it gradually.** Add the path to the facade and shift a small slice of traffic, then widen. Watch latency and error rates during each step, and keep the monolith path warm as a fallback.
    
5.  **Verify parity, then delete.** Once the new service matches the monolith’s behavior under real traffic, remove that code from the monolith. Deleting the old path is what makes the win permanent.
    
6.  **Repeat, and know when to stop.** Move to the next seam. Stop when what remains is small and stable enough that extracting it would cost more than it returns.
    

Careful here

The most dangerous shortcut is pointing a new service at the monolith’s database so you can “extract later.” That gives you two services coupled through one schema, which is a distributed monolith: all of the network overhead, none of the independence. Give the service its own data, even if that means a sync period during the transition.

Data is the genuinely hard part, and it is worth being honest about that. Moving stateless request handling is straightforward; moving the data it owns without downtime is not. The workable approach is to give the new service its own store, backfill it, keep it in sync while both paths run, and cut the monolith’s write path over only once the new service is authoritative and verified. Where strict consistency is required during the overlap, treat the monolith as the source of truth until the very last step.

Tip

Measure the migration by how much of the monolith is gone, not by how many services exist. A useful signal is the share of production traffic served by extracted services and the amount of code deleted from the monolith. Creating services without deleting code from the original is motion without progress.

## [Results](#results)

The application moved onto EKS without a single cutover event and without a feature freeze. Because each route shifted gradually with a live fallback, no migration step was a high-stakes moment; the riskiest change only ever touched a small slice of one path at a time. Independent deploys arrived for each extracted service, so teams stopped blocking each other, and the hot paths could scale on their own instead of forcing the whole application to scale with them.

The migration also did not finish in the storybook sense, and that was the right outcome. A stable, low-change remainder of the monolith stayed in place, containerized and behind the facade, because extracting it would have cost more than it returned. Treat “the monolith is gone” as a possible ending, not the goal.

## [Lessons](#lessons)

The facade is the whole safety story. Because every request always had a valid destination and an instant fallback, no step was irreversible. That single property is what let the team move quickly instead of cautiously.

Extract the easy seams first. The instinct to start with the messy core is a trap. Early, low-risk extractions build the tooling and the team’s confidence, so the hard ones later are routine instead of terrifying.

Data ownership is the real migration. The service boundary is easy; the data boundary is the work. Any plan that hand-waves the database is a plan to build a distributed monolith.

Give yourself permission to stop. The goal was never zero monolith. It was independent, deployable, scalable services for the parts that needed it, and a small stable remainder for the parts that did not.

## [Frequently Asked Questions](#frequently-asked-questions)

It is an incremental migration approach where a new system grows around an old one and takes over its responsibilities one at a time, until the old system is replaced or reduced to a small remainder. A routing facade sits in front and directs each request to either the new component or the old one, so both run in production together and you never need a single cutover.

Because a cutover is a single high-stakes event with no safe rollback. You freeze features to build the replacement, it drifts from the original as the original keeps changing, and if anything breaks on migration night your only option is reverting everything at once. Strangler-fig keeps the old system live the whole time, so every step is small and reversible.

Low coupling and a clear data owner. Pick a capability with a clean boundary that mostly owns its own data, like users or billing, so you are not untangling shared state on your first attempt. Early, low-risk wins build the tooling and the confidence you will need for the harder seams later.

Give each extracted service its own store rather than pointing it at the monolith’s schema. Backfill it and keep it in sync while both paths run, then cut the monolith’s write path over only once the new service is authoritative and verified. Sharing one database across services is a distributed monolith and defeats the point of the migration.

When the remaining monolith is small and stable enough that extracting more would cost more than it returns. Track the share of production traffic served by extracted services and the amount of code deleted from the monolith. Done does not have to mean zero monolith; a low-change remainder behind the facade is a perfectly good ending.

## [References](#references)

-   [Martin Fowler: StranglerFigApplication](https://martinfowler.com/bliki/StranglerFigApplication.html)
-   [Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html)
-   [AWS Prescriptive Guidance: strangler fig pattern](https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-decomposing-monoliths/strangler-fig.html)
-   [Kubernetes Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/)
-   [Database decomposition patterns](https://microservices.io/patterns/data/database-per-service.html)

Was this useful?

## Tags

[#Kubernetes](/case-studies/tags/kubernetes)[#EKS](/case-studies/tags/eks)[#Migration](/case-studies/tags/migration)[#Strangler Pattern](/case-studies/tags/strangler-pattern)[#Microservices](/case-studies/tags/microservices)[#Traffic Shifting](/case-studies/tags/traffic-shifting)[#Rollback](/case-studies/tags/rollback)

## Share

[Facebook](https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmkabumattar.com%2Fcase-studies%2Fpost%2Fmonolith-to-kubernetes-strangler-migration "Share on Facebook")[Twitter](https://twitter.com/intent/tweet/?text=Migrating%20a%20Monolith%20to%20Kubernetes%20Without%20a%20Big-Bang%20Cutover&url=https%3A%2F%2Fmkabumattar.com%2Fcase-studies%2Fpost%2Fmonolith-to-kubernetes-strangler-migration "Share on Twitter")[LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmkabumattar.com%2Fcase-studies%2Fpost%2Fmonolith-to-kubernetes-strangler-migration&title=Migrating%20a%20Monolith%20to%20Kubernetes%20Without%20a%20Big-Bang%20Cutover&summary=Using%20the%20strangler-fig%20pattern%20to%20move%20a%20large%20monolith%20onto%20EKS%20service%20by%20service%2C%20with%20a%20routing%20facade%2C%20gradual%20traffic%20shifting%2C%20and%20a%20rollback%20at%20every%20step.&source=https://mkabumattar.com "Share on LinkedIn")[WhatsApp](https://wa.me/?text=Migrating%20a%20Monolith%20to%20Kubernetes%20Without%20a%20Big-Bang%20Cutover%20https%3A%2F%2Fmkabumattar.com%2Fcase-studies%2Fpost%2Fmonolith-to-kubernetes-strangler-migration "Share on WhatsApp")[Telegram](https://t.me/share/url?url=https%3A%2F%2Fmkabumattar.com%2Fcase-studies%2Fpost%2Fmonolith-to-kubernetes-strangler-migration&text=Migrating%20a%20Monolith%20to%20Kubernetes%20Without%20a%20Big-Bang%20Cutover "Share on Telegram")[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fmkabumattar.com%2Fcase-studies%2Fpost%2Fmonolith-to-kubernetes-strangler-migration&title=Migrating%20a%20Monolith%20to%20Kubernetes%20Without%20a%20Big-Bang%20Cutover "Share on Reddit")[Hacker News](http://news.ycombinator.com/submitlink?u=https%3A%2F%2Fmkabumattar.com%2Fcase-studies%2Fpost%2Fmonolith-to-kubernetes-strangler-migration&t=Migrating%20a%20Monolith%20to%20Kubernetes%20Without%20a%20Big-Bang%20Cutover "Share on Hacker News")[Pinterest](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmkabumattar.com%2Fcase-studies%2Fpost%2Fmonolith-to-kubernetes-strangler-migration&media=&description=Using%20the%20strangler-fig%20pattern%20to%20move%20a%20large%20monolith%20onto%20EKS%20service%20by%20service%2C%20with%20a%20routing%20facade%2C%20gradual%20traffic%20shifting%2C%20and%20a%20rollback%20at%20every%20step. "Share on Pinterest")[Email](<mailto:?subject=Migrating%20a%20Monolith%20to%20Kubernetes%20Without%20a%20Big-Bang%20Cutover&body=Check out this article: https%3A%2F%2Fmkabumattar.com%2Fcase-studies%2Fpost%2Fmonolith-to-kubernetes-strangler-migration>)

## Comments

## You might also enjoy

More posts on similar topics

[![Cutting a SaaS AWS Bill 41% Without Slowing Delivery](/_astro/hero.DJTB593d_Z1iASgs.webp)](/case-studies/post/aws-cost-optimization-saas-case-study)

## [Cutting a SaaS AWS Bill 41% Without Slowing Delivery](/case-studies/post/aws-cost-optimization-saas-case-study)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Cloud Computing](/case-studies/categories/cloud-computing)
-   [DevOps](/case-studies/categories/devops)
-   [Cloud Native](/case-studies/categories/cloud-native)

A growing SaaS ran on EKS with a full GitOps pipeline, and it was over its AWS budget nearly every month. The reflex from leadership was the usual one: freeze features until the bill comes down. That

[#AWS](/case-studies/tags/aws)[#EKS](/case-studies/tags/eks)[#Kubernetes](/case-studies/tags/kubernetes)+7 tags

[read more](/case-studies/post/aws-cost-optimization-saas-case-study)

[![Building an Internal Developer Platform on Backstage and GitOps](/_astro/hero.Dq3xrist_1gdSPN.webp)](/case-studies/post/internal-developer-platform-backstage-gitops)

## [Building an Internal Developer Platform on Backstage and GitOps](/case-studies/post/internal-developer-platform-backstage-gitops)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps](/case-studies/categories/devops)
-   [Platform Engineering](/case-studies/categories/platform-engineering)
-   [Cloud Native](/case-studies/categories/cloud-native)

Product teams were spending more time waiting on the platform team than building features. Spinning up a new service meant opening a ticket and waiting for someone to provision a repo, wire up CI, wri

[#Backstage](/case-studies/tags/backstage)[#GitOps](/case-studies/tags/gitops)[#Argo CD](/case-studies/tags/argo-cd)+4 tags

[read more](/case-studies/post/internal-developer-platform-backstage-gitops)

[![QuenchWorks: Building a 0-CVE Container Image and Helm Chart Catalog](/_astro/hero.BfjMKoMg_ZI2zvl.webp)](/case-studies/post/quenchworks-zero-cve-catalog)

## [QuenchWorks: Building a 0-CVE Container Image and Helm Chart Catalog](/case-studies/post/quenchworks-zero-cve-catalog)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Security](/case-studies/categories/security)
-   [DevOps](/case-studies/categories/devops)
-   [Cloud Native](/case-studies/categories/cloud-native)

When Bitnami moved its long-trusted catalog behind a paid tier, thousands of teams woke up to a supply-chain problem they didn't choose. The free images they had pinned in production would stop gettin

[#Containers](/case-studies/tags/containers)[#Wolfi](/case-studies/tags/wolfi)[#Helm](/case-studies/tags/helm)+5 tags

[read more](/case-studies/post/quenchworks-zero-cve-catalog)

[![Multi-Region Active-Active for a Payments API](/_astro/hero.RPbRRCdE_flG0g.webp)](/case-studies/post/multi-region-active-active-payments)

## [Multi-Region Active-Active for a Payments API](/case-studies/post/multi-region-active-active-payments)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Architecture](/case-studies/categories/architecture)
-   [Cloud Computing](/case-studies/categories/cloud-computing)
-   [Reliability](/case-studies/categories/reliability)

A payments API that moves real money had been running comfortably in a single AWS region for years. It was reliable until the day it was not: a regional control-plane incident took the whole service o

[#Multi Region](/case-studies/tags/multi-region)[#Active Active](/case-studies/tags/active-active)[#Payments](/case-studies/tags/payments)+5 tags

[read more](/case-studies/post/multi-region-active-active-payments)

[![Zero-Downtime PostgreSQL Major-Version Upgrade at Scale](/_astro/hero.C03RcOLI_141vsK.webp)](/case-studies/post/zero-downtime-postgres-upgrade)

## [Zero-Downtime PostgreSQL Major-Version Upgrade at Scale](/case-studies/post/zero-downtime-postgres-upgrade)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps](/case-studies/categories/devops)
-   [Databases](/case-studies/categories/databases)
-   [Cloud Computing](/case-studies/categories/cloud-computing)

A multi-terabyte PostgreSQL 12 database was reaching end of life, and the business ran around the clock, so the usual answer of "schedule a maintenance window" was off the table. We upgraded it to Pos

[#PostgreSQL](/case-studies/tags/postgresql)[#Logical Replication](/case-studies/tags/logical-replication)[#Zero Downtime](/case-studies/tags/zero-downtime)+3 tags

[read more](/case-studies/post/zero-downtime-postgres-upgrade)

5 related posts
