DEV Community

Thej Deep
Thej Deep

Posted on

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

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:

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

Prerequisites

  • IBM Cloud account
  • An existing IKS cluster
  • Tools installed locally:
    • ibmcloud
    • kubectl
    • docker

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Verify it exists:

ibmcloud cr images | grep monolith
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Apply and confirm rollout:

kubectl apply -f deployment.yaml
kubectl rollout status deploy/monolith
kubectl get pods -l app=monolith
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Apply:

kubectl apply -f service.yaml
kubectl get svc monolith-svc
Enter fullscreen mode Exit fullscreen mode

Quick local test:

kubectl port-forward svc/monolith-svc 8080:80
curl -i http://localhost:8080/health
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Apply:

kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide
Enter fullscreen mode Exit fullscreen mode

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" });
});
Enter fullscreen mode Exit fullscreen mode

Dockerfile for the new service:

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Apply:

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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Apply:

kubectl apply -f ingress.yaml
kubectl get ingress app-ingress -o wide
Enter fullscreen mode Exit fullscreen mode

Test:

curl http://<APP_DOMAIN>/api/auth/ping
Enter fullscreen mode Exit fullscreen mode

Expected:

{"service":"auth-service","status":"pong"}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Option B: Undo the deployment rollout

kubectl rollout undo deploy/auth-service
Enter fullscreen mode Exit fullscreen mode

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)