Tools
Tools: Strangler Fig on IBM Kubernetes: Modernizing a Monolith Without Breaking Production
2026-02-04
0 views
admin
Why the Strangler Fig Pattern Still Works ## What You Will Build ## Prerequisites ## Step 1: Log in to IBM Cloud and Connect to Kubernetes ## Step 2: Create a Dedicated Namespace ## Step 3: Containerize the Existing Monolith ## Step 4: Push the Image to IBM Cloud Container Registry ## Step 5: Deploy the Monolith to Kubernetes ## Step 6: Put the Monolith Behind Ingress ## Step 7: Pick the First “Edge” Capability to Extract ## Step 8: Build the New Edge Service (auth-service) ## Step 9: Deploy the New Service to Kubernetes ## Step 10: Strangle Traffic Using Ingress Routing ## Step 11: Rollback Strategy ## Step 12: Repeat the Pattern Safely ## What You Achieved ## Final Thoughts 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: 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. By the end of this guide, you will: Keep modernization isolated and easy to clean up later. The goal here is no behavior change, just package the monolith. Example Dockerfile (Node.js monolith) Add minimal health endpoints (if you don’t already have them) Log in to the registry and create a namespace (one-time): Tag and push your image: 5.1 Deployment manifest (deployment.yaml) Apply and confirm rollout: 5.2 Service manifest (service.yaml) Ingress becomes your routing control plane for strangling. At this point: 100% traffic still goes to the monolith. Start with something: low risk
clear boundaries
minimal writes /api/auth/*
/api/reporting/*
read-only catalog endpoints For this walkthrough, we’ll extract: Minimal example endpoint: Dockerfile for the new service: 9.1 Deployment (auth-deploy.yaml) 9.2 Service (auth-svc.yaml) Update ingress.yaml so /api/auth/* routes to the new service, and everything else stays on the monolith: 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: Option B: Undo the deployment rollout Once the first extracted capability is stable: Choose the next bounded domain Build it as a separate service Route it with Ingress Keep rollback available at every step the monolith shrinks
risk decreases modernization becomes routine rather than a “big migration” No downtime
No big rewrite
Production-safe modernization
Kubernetes as an enabler, not a forcing function 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. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK:
ibmcloud login -a https://cloud.ibm.com
ibmcloud target -r <REGION> -g <RESOURCE_GROUP> Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
ibmcloud login -a https://cloud.ibm.com
ibmcloud target -r <REGION> -g <RESOURCE_GROUP> COMMAND_BLOCK:
ibmcloud login -a https://cloud.ibm.com
ibmcloud target -r <REGION> -g <RESOURCE_GROUP> COMMAND_BLOCK:
kubectl create namespace monolith-demo
kubectl config set-context --current --namespace=monolith-demo
kubectl get ns Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
kubectl create namespace monolith-demo
kubectl config set-context --current --namespace=monolith-demo
kubectl get ns COMMAND_BLOCK:
kubectl create namespace monolith-demo
kubectl config set-context --current --namespace=monolith-demo
kubectl get ns CODE_BLOCK:
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"] Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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"] CODE_BLOCK:
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"] COMMAND_BLOCK:
// 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 . Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// 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 . COMMAND_BLOCK:
// 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 . CODE_BLOCK:
ibmcloud cr login
ibmcloud cr namespace-add <REGISTRY_NAMESPACE> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
ibmcloud cr login
ibmcloud cr namespace-add <REGISTRY_NAMESPACE> CODE_BLOCK:
ibmcloud cr login
ibmcloud cr namespace-add <REGISTRY_NAMESPACE> COMMAND_BLOCK:
docker tag monolith:1.0.0 <REGISTRY>/<REGISTRY_NAMESPACE>/monolith:1.0.0
docker push <REGISTRY>/<REGISTRY_NAMESPACE>/monolith:1.0.0 Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
docker tag monolith:1.0.0 <REGISTRY>/<REGISTRY_NAMESPACE>/monolith:1.0.0
docker push <REGISTRY>/<REGISTRY_NAMESPACE>/monolith:1.0.0 COMMAND_BLOCK:
docker tag monolith:1.0.0 <REGISTRY>/<REGISTRY_NAMESPACE>/monolith:1.0.0
docker push <REGISTRY>/<REGISTRY_NAMESPACE>/monolith:1.0.0 CODE_BLOCK:
ibmcloud cr images | grep monolith Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
ibmcloud cr images | grep monolith CODE_BLOCK:
ibmcloud cr images | grep monolith CODE_BLOCK:
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 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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 CODE_BLOCK:
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 COMMAND_BLOCK:
kubectl apply -f deployment.yaml
kubectl rollout status deploy/monolith
kubectl get pods -l app=monolith Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
kubectl apply -f deployment.yaml
kubectl rollout status deploy/monolith
kubectl get pods -l app=monolith COMMAND_BLOCK:
kubectl apply -f deployment.yaml
kubectl rollout status deploy/monolith
kubectl get pods -l app=monolith CODE_BLOCK:
apiVersion: v1
kind: Service
metadata: name: monolith-svc
spec: selector: app: monolith ports: - name: http port: 80 targetPort: 8080 type: ClusterIP Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
apiVersion: v1
kind: Service
metadata: name: monolith-svc
spec: selector: app: monolith ports: - name: http port: 80 targetPort: 8080 type: ClusterIP CODE_BLOCK:
apiVersion: v1
kind: Service
metadata: name: monolith-svc
spec: selector: app: monolith ports: - name: http port: 80 targetPort: 8080 type: ClusterIP COMMAND_BLOCK:
kubectl apply -f service.yaml
kubectl get svc monolith-svc Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
kubectl apply -f service.yaml
kubectl get svc monolith-svc COMMAND_BLOCK:
kubectl apply -f service.yaml
kubectl get svc monolith-svc COMMAND_BLOCK:
kubectl port-forward svc/monolith-svc 8080:80
curl -i http://localhost:8080/health Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
kubectl port-forward svc/monolith-svc 8080:80
curl -i http://localhost:8080/health COMMAND_BLOCK:
kubectl port-forward svc/monolith-svc 8080:80
curl -i http://localhost:8080/health CODE_BLOCK:
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 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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 CODE_BLOCK:
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 COMMAND_BLOCK:
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide COMMAND_BLOCK:
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide COMMAND_BLOCK:
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" });
}); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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" });
}); COMMAND_BLOCK:
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" });
}); CODE_BLOCK:
FROM node:20-alpine
WORKDIR /app
COPY . .
EXPOSE 8081
CMD ["node","server.js"] Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
FROM node:20-alpine
WORKDIR /app
COPY . .
EXPOSE 8081
CMD ["node","server.js"] CODE_BLOCK:
FROM node:20-alpine
WORKDIR /app
COPY . .
EXPOSE 8081
CMD ["node","server.js"] COMMAND_BLOCK:
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 Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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 COMMAND_BLOCK:
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 CODE_BLOCK:
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 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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 CODE_BLOCK:
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 COMMAND_BLOCK:
kubectl apply -f auth-deploy.yaml
kubectl rollout status deploy/auth-service
kubectl get pods -l app=auth-service Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
kubectl apply -f auth-deploy.yaml
kubectl rollout status deploy/auth-service
kubectl get pods -l app=auth-service COMMAND_BLOCK:
kubectl apply -f auth-deploy.yaml
kubectl rollout status deploy/auth-service
kubectl get pods -l app=auth-service CODE_BLOCK:
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 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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 CODE_BLOCK:
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 CODE_BLOCK:
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 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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 CODE_BLOCK:
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 COMMAND_BLOCK:
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide COMMAND_BLOCK:
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide COMMAND_BLOCK:
curl http://<APP_DOMAIN>/api/auth/ping Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
curl http://<APP_DOMAIN>/api/auth/ping COMMAND_BLOCK:
curl http://<APP_DOMAIN>/api/auth/ping CODE_BLOCK:
{"service":"auth-service","status":"pong"} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
{"service":"auth-service","status":"pong"} CODE_BLOCK:
{"service":"auth-service","status":"pong"} COMMAND_BLOCK:
kubectl apply -f ingress.yaml Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
kubectl apply -f ingress.yaml COMMAND_BLOCK:
kubectl apply -f ingress.yaml COMMAND_BLOCK:
kubectl rollout undo deploy/auth-service Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
kubectl rollout undo deploy/auth-service COMMAND_BLOCK:
kubectl rollout undo deploy/auth-service - Long delivery cycles
- High data risk
- Business disruption - 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 - IBM Cloud account
- An existing IKS cluster
- Tools installed locally: ibmcloud
kubectl
docker
how-totutorialguidedev.toaimlservernetworknetworkingroutingdockernodekubernetesk8s