Strangler Fig on IBM Kubernetes: Modernizing a Monolith Without Breaking Production

Published: (February 3, 2026 at 07:18 PM EST)
5 min read
Source: Dev.to

Source: Dev.to

By the end of this guide you will be able to:

  1. Containerize an existing monolithic application
  2. Deploy it to IBM Cloud Kubernetes Service
  3. Place it behind an Ingress
  4. Deploy a new “edge” service
  5. Route traffic gradually using path‑based routing
  6. Keep rollback simple and safe

Prerequisites

ItemDetails
IBM Cloud account
Existing IKS cluster
Local toolsibmcloud, kubectl, docker
Loginbash<br>ibmcloud login -a https://cloud.ibm.com<br>ibmcloud target -r <region> -g <resource-group><br>

1. Set up a clean namespace

kubectl create namespace monolith-demo
kubectl config set-context --current --namespace=monolith-demo
kubectl get ns

Goal: No behavior change – just package the monolith.

2. Containerize the monolith

Dockerfile (Node.js monolith)

# ---- Build stage ----
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .

# ---- Runtime stage ----
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 .

Push to IBM Cloud Container Registry

# Log in (one‑time)
ibmcloud cr login
ibmcloud cr namespace-add <your-namespace>

# Tag & push
docker tag monolith:1.0.0 <registry>/<namespace>/monolith:1.0.0
docker push <registry>/<namespace>/monolith:1.0.0

# Verify
ibmcloud cr images | grep monolith

3. Deploy the monolith

3.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>/<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
kubectl apply -f deployment.yaml
kubectl rollout status deploy/monolith
kubectl get pods -l app=monolith

3.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
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

4. Expose via Ingress (the routing control plane)

Ingress manifest (ingress.yaml)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
spec:
  rules:
    - host: <your-host>
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: monolith-svc
                port:
                  number: 80
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide

At this point: 100 % of traffic still goes to the monolith.

5. Choose a low‑risk first slice to “strangle”

Good first candidates: /api/auth/*.
For this walkthrough we’ll extract the auth API.

Minimal example endpoint (add to the monolith)

app.get("/api/auth/ping", (req, res) => {
  res.json({ service: "auth-service", status: "pong" });
});

6. Build the new Auth service

Dockerfile (Dockerfile)

FROM node:20-alpine
WORKDIR /app
COPY . .
EXPOSE 8081
CMD ["node","server.js"]

Build & push

docker build -t auth-service:1.0.0 .
docker tag auth-service:1.0.0 <registry>/<namespace>/auth-service:1.0.0
docker push <registry>/<namespace>/auth-service:1.0.0

7. Deploy the Auth service

7.1 Deployment manifest (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>/<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
kubectl apply -f auth-deploy.yaml
kubectl rollout status deploy/auth-service
kubectl get pods -l app=auth-service

7.2 Service manifest (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
kubectl apply -f auth-svc.yaml
kubectl get svc auth-svc

8. Update Ingress to route /api/auth/* to the new service

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
spec:
  rules:
    - host: <your-host>
      http:
        paths:
          # New route for auth
          - path: /api/auth
            pathType: Prefix
            backend:
              service:
                name: auth-svc
                port:
                  number: 80
          # Fallback to monolith for everything else
          - path: /
            pathType: Prefix
            backend:
              service:
                name: monolith-svc
                port:
                  number: 80
kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide

Now traffic to /api/auth/* is served by the new auth‑service, while all other requests continue to hit the monolith.

9. Gradual rollout & rollback

  1. Validate the new endpoint

    curl -i https://<your-host>/api/auth/ping
  2. Increase traffic (if using a traffic‑splitting controller) or simply monitor the new service’s metrics and logs.

  3. Rollback (if needed)

    # Remove the auth path from the Ingress
    kubectl edit ingress app-ingress   # delete the /api/auth block
    # Or delete the auth deployment/service
    kubectl delete -f auth-deploy.yaml
    kubectl delete -f auth-svc.yaml

Because the monolith remains untouched behind the Ingress, you can always revert instantly.

10. Clean‑up (when you’re ready)

kubectl delete -f ingress.yaml
kubectl delete -f service.yaml
kubectl delete -f deployment.yaml
kubectl delete namespace monolith-demo

🎉 You’ve just applied the Strangler Fig pattern in a real IBM Cloud Kubernetes environment!

Continue extracting additional functional slices (e.g., /api/orders/*, /api/payments/*) using the same approach until the monolith can be retired safely.

Keep rollback boring and fast

Option A – Route back to monolith

Edit the 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

Process

  1. Stabilize the first extracted capability
  2. Choose the next bounded domain
  3. Build it as a separate service
  4. Deploy it
  5. Route it with Ingress
  6. Keep rollback available at every step

Over time

  • The monolith shrinks.
  • Modernization becomes routine rather than a “big migration”.
  • No downtime.

The Strangler Fig pattern works because it respects reality – you modernize without deleting the past.

If you’re sitting on a monolith today, this approach lets you move forward without breaking what already works.

Back to Blog

Related posts

Read more »