Why the Strangler Fig Pattern Still Works
Most enterprise monoliths don’t fail because of bad code.
They fail because changing them safely becomes too risky.
A full rewrite to microservices sounds attractive, but in practice it often leads to:
- Long delivery cycles
- High data risk
- Business disruption
The Strangler Fig pattern offers a safer alternative:
modernize incrementally while keeping the system running.
In this article, I walk through a step-by-step, production-safe approach to applying the Strangler Fig pattern using IBM Cloud Kubernetes Service (IKS), including real commands and manifests you can run.
What You Will Build
By the end of this guide, you will:
- Containerize an existing monolithic application
- Deploy it to IBM Cloud Kubernetes Service
- Place it behind Ingress
- Deploy a new “edge” service
- Route traffic gradually using path-based routing
- Keep rollback simple and safe
Prerequisites
- IBM Cloud account
- An existing IKS cluster
- Tools installed locally:
ibmcloudkubectldocker
Step 1: Log in to IBM Cloud and Connect to Kubernetes
ibmcloud login -a https://cloud.ibm.com
ibmcloud target -r <REGION> -g <RESOURCE_GROUP>
Step 2: Create a Dedicated Namespace
Keep modernization isolated and easy to clean up later.
kubectl create namespace monolith-demo
kubectl config set-context --current --namespace=monolith-demo
kubectl get ns
Step 3: Containerize the Existing Monolith
The goal here is no behavior change, just package the monolith.
Example Dockerfile (Node.js monolith)
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app /app
EXPOSE 8080
CMD ["npm","start"]
Add minimal health endpoints (if you don’t already have them)
// Example endpoints
app.get("/health", (req, res) => res.status(200).send("ok"));
app.get("/ready", (req, res) => res.status(200).send("ready"));
Build the image:
docker build -t monolith:1.0.0 .
Step 4: Push the Image to IBM Cloud Container Registry
Log in to the registry and create a namespace (one-time):
ibmcloud cr login
ibmcloud cr namespace-add <REGISTRY_NAMESPACE>
Tag and push your image:
docker tag monolith:1.0.0 <REGISTRY>/<REGISTRY_NAMESPACE>/monolith:1.0.0
docker push <REGISTRY>/<REGISTRY_NAMESPACE>/monolith:1.0.0
Verify it exists:
ibmcloud cr images | grep monolith
Step 5: Deploy the Monolith to Kubernetes
5.1 Deployment manifest (deployment.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: monolith
spec:
replicas: 2
selector:
matchLabels:
app: monolith
template:
metadata:
labels:
app: monolith
spec:
containers:
- name: monolith
image: <REGISTRY>/<REGISTRY_NAMESPACE>/monolith:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
Apply and confirm rollout:
kubectl apply -f deployment.yaml
kubectl rollout status deploy/monolith
kubectl get pods -l app=monolith
5.2 Service manifest (service.yaml)
apiVersion: v1
kind: Service
metadata:
name: monolith-svc
spec:
selector:
app: monolith
ports:
- name: http
port: 80
targetPort: 8080
type: ClusterIP
Apply:
kubectl apply -f service.yaml
kubectl get svc monolith-svc
Quick local test:
kubectl port-forward svc/monolith-svc 8080:80
curl -i http://localhost:8080/health
Step 6: Put the Monolith Behind Ingress
Ingress becomes your routing control plane for strangling.
Create ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
spec:
rules:
- host: <APP_DOMAIN>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: monolith-svc
port:
number: 80
Apply:
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide
At this point: 100% traffic still goes to the monolith.
Step 7: Pick the First “Edge” Capability to Extract
Start with something:
low risk
clear boundaries
minimal writes
Good first choices:
/api/auth/*
/api/reporting/*
read-only catalog endpoints
For this walkthrough, we’ll extract:
/api/auth/*
Step 8: Build the New Edge Service (auth-service)
Minimal example endpoint:
app.get("/health", (req, res) => res.status(200).send("ok"));
app.get("/ready", (req, res) => res.status(200).send("ready"));
app.get("/api/auth/ping", (req, res) => {
res.json({ service: "auth-service", status: "pong" });
});
Dockerfile for the new service:
FROM node:20-alpine
WORKDIR /app
COPY . .
EXPOSE 8081
CMD ["node","server.js"]
Build and push:
docker build -t auth-service:1.0.0 .
docker tag auth-service:1.0.0 <REGISTRY>/<REGISTRY_NAMESPACE>/auth-service:1.0.0
docker push <REGISTRY>/<REGISTRY_NAMESPACE>/auth-service:1.0.0
Step 9: Deploy the New Service to Kubernetes
9.1 Deployment (auth-deploy.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-service
spec:
replicas: 2
selector:
matchLabels:
app: auth-service
template:
metadata:
labels:
app: auth-service
spec:
containers:
- name: auth-service
image: <REGISTRY>/<REGISTRY_NAMESPACE>/auth-service:1.0.0
ports:
- containerPort: 8081
readinessProbe:
httpGet:
path: /ready
port: 8081
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8081
initialDelaySeconds: 15
periodSeconds: 10
Apply:
kubectl apply -f auth-deploy.yaml
kubectl rollout status deploy/auth-service
kubectl get pods -l app=auth-service
9.2 Service (auth-svc.yaml)
apiVersion: v1
kind: Service
metadata:
name: auth-svc
spec:
selector:
app: auth-service
ports:
- name: http
port: 80
targetPort: 8081
type: ClusterIP
Apply:
kubectl apply -f auth-svc.yaml
kubectl get svc auth-svc
Step 10: Strangle Traffic Using Ingress Routing
Update ingress.yaml so /api/auth/* routes to the new service, and everything else stays on the monolith:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
spec:
rules:
- host: <APP_DOMAIN>
http:
paths:
- path: /api/auth
pathType: Prefix
backend:
service:
name: auth-svc
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: monolith-svc
port:
number: 80
Apply:
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide
Test:
curl http://<APP_DOMAIN>/api/auth/ping
Expected:
{"service":"auth-service","status":"pong"}
Step 11: Rollback Strategy
Keep rollback boring and fast.
Option A: Route back to monolith
Edit Ingress and remove the /api/auth path (or point it to monolith-svc), then re-apply:
kubectl apply -f ingress.yaml
Option B: Undo the deployment rollout
kubectl rollout undo deploy/auth-service
Step 12: Repeat the Pattern Safely
Once the first extracted capability is stable:
Choose the next bounded domain
Build it as a separate service
Deploy it
Route it with Ingress
Keep rollback available at every step
Over time:
the monolith shrinks
risk decreases
modernization becomes routine rather than a “big migration”
What You Achieved
No downtime
No big rewrite
Production-safe modernization
Kubernetes as an enabler, not a forcing function
Final Thoughts
The Strangler Fig pattern works because it respects reality.
You don’t modernize by deleting the past.
You modernize by outgrowing it safely.
If you’re sitting on a monolith today, this approach lets you move forward without breaking what already works.
Top comments (0)