Skip to content
← Back to the blog

How to migrate to Kubernetes with zero downtime: a step-by-step guide

A practical guide to moving production workloads to Kubernetes incrementally, with no interruption for your users and no surprises at 3am.

Cover: How to migrate to Kubernetes with zero downtime

Migrating to Kubernetes can feel like a high-risk operation. Most teams treat it as a “big bang”: stop production, move everything at once, pray it works. There is a better way.

The core principle: migrate incrementally

The trick is to treat the migration as a series of reversible steps rather than a single event. At every step Kubernetes runs alongside your existing infrastructure until you are confident everything works.

Phase 1: Prepare the environment without touching production

Before moving a single workload, you need a staging cluster configured exactly like the one you will run in production.

# Create an EKS cluster with eksctl
eksctl create cluster \
  --name evoltix-staging \
  --region eu-central-1 \
  --nodegroup-name workers \
  --node-type t3.medium \
  --nodes 2

At this point production is still running exactly as before. You have changed nothing.

Phase 2: Containerise the services

Every service needs its own Dockerfile. The golden rule: one process per container.

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]

Test the container locally before pushing it to any registry:

docker build -t my-service:latest .
docker run -p 3000:3000 my-service:latest

Phase 3: The first deployment to staging

A basic Kubernetes Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-service
  template:
    metadata:
      labels:
        app: my-service
    spec:
      containers:
      - name: my-service
        image: my-registry/my-service:latest
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"

Do not skip resources. Without limits, a single pod with a memory leak can take down the whole node.

Phase 4: Cutting traffic over with zero downtime

This is the heart of a downtime-free migration. We use a dual load balancer: 5% of traffic goes to Kubernetes first, the rest stays on the old infrastructure.

On AWS this is implemented with ALB weighted target groups:

resource "aws_lb_listener_rule" "canary" {
  listener_arn = aws_lb_listener.main.arn

  action {
    type = "forward"
    forward {
      target_group {
        arn    = aws_lb_target_group.old.arn
        weight = 95
      }
      target_group {
        arn    = aws_lb_target_group.kubernetes.arn
        weight = 5
      }
    }
  }
}

You watch the metrics for 24 to 48 hours. If everything holds, you raise the weight gradually: 5% → 20% → 50% → 100%.

If something breaks, rolling back is setting the weight back to 0. In seconds.

What not to do

  • Do not migrate the database at the same time. Stateless services move to Kubernetes first; databases come later, and with far more care.
  • Do not ignore health checks. Kubernetes needs to know when a pod is actually ready to receive traffic.
  • Do not put secrets in the Deployment’s environment variables. Use Kubernetes Secrets or, better, AWS Secrets Manager with the CSI driver.

What you end up with

Once the migration is done, you have:

  • Deployments in minutes instead of hours
  • Automatic rollback when a health check fails
  • Horizontal scaling with a single command
  • Reproducible infrastructure, defined as code

Is your team considering a move to Kubernetes? Tell us about your case — we run this migration for companies of every size.