How to Perform a Blue-Green Deployment in Kubernetes

How to Perform a Blue-Green Deployment in Kubernetes

If you’ve ever pushed a new release and then spent the next hour anxiously refreshing dashboards, hoping nothing breaks, you already understand why blue-green deployments exist. I’ve been burned by rolling updates that looked fine in staging and then quietly degraded production for twenty minutes before anyone noticed. Blue-green deployment is one of the cleanest ways I know to eliminate that anxiety, because it gives you an instant, atomic switch between the old version and the new one — and just as importantly, an instant way back.

In this guide I’ll walk through what blue-green deployment actually means in a Kubernetes context, how it differs from rolling updates and canary releases, and then get hands-on with real YAML manifests, kubectl commands, and a working example you can adapt to your own cluster.

What Is Blue-Green Deployment?

The idea is simple. You run two identical production environments — call them “blue” and “green.” At any given time, only one of them is live, receiving real traffic. The other sits idle, or is used for final testing. When you’re ready to release a new version, you deploy it to the idle environment, test it thoroughly, and then flip a switch (usually a load balancer or Service selector) to send traffic to the new environment. If something goes wrong, you flip the switch back. No rollback deployment, no waiting for pods to terminate — just an instant cutover.

This is fundamentally different from a rolling update, where old and new pods coexist during the rollout, and different from a canary release, where only a small percentage of traffic goes to the new version initially. Blue-green is all-or-nothing, which makes it predictable but also means you need double the resources during the transition window.

Kubernetes Architecture Basics You Need First

Before diving into the mechanics, it helps to understand the pieces Kubernetes gives us to build this pattern:

  • Pods are the smallest deployable unit — one or more containers sharing storage and network.
  • Deployments manage ReplicaSets, which in turn manage Pods, giving you declarative updates and rollback history.
  • Services provide a stable network identity and load-balance traffic to a set of Pods selected by labels.
  • Labels and selectors are the glue. A Service doesn’t know about specific Pods; it just watches for Pods matching a label selector.

Blue-green deployment in Kubernetes exploits that last point. Instead of updating a Deployment in place, you create a second Deployment with a different label (like version: green), and then change the Service’s selector to point at it when you’re ready.

Step 1: Deploy the “Blue” Version

Let’s say we’re running a simple web app. Here’s the initial Deployment manifest:

# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-blue
  labels:
    app: myapp
    version: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
        - name: myapp
          image: myrepo/myapp:1.0.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10

Apply it:

kubectl apply -f blue-deployment.yaml

Now create a Service that points to the blue version:

# myapp-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
    version: blue
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: ClusterIP
kubectl apply -f myapp-service.yaml
kubectl get pods -l version=blue

Expected output:

NAME                          READY   STATUS    RESTARTS   AGE
myapp-blue-7d4f9c9b8f-2xk9q   1/1     Running   0          2m
myapp-blue-7d4f9c9b8f-8mzp1   1/1     Running   0          2m
myapp-blue-7d4f9c9b8f-vq7rd   1/1     Running   0          2m

Step 2: Deploy the “Green” Version

Now, when you’re ready to ship version 2.0.0, you don’t touch the blue Deployment at all. Instead, you create a completely separate green Deployment:

# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-green
  labels:
    app: myapp
    version: green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
        - name: myapp
          image: myrepo/myapp:2.0.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
kubectl apply -f green-deployment.yaml
kubectl rollout status deployment/myapp-green

At this point, blue is still serving all live traffic. Green is running but isolated. I like to test it directly using a temporary port-forward or a separate internal Service before touching production traffic at all:

kubectl port-forward deployment/myapp-green 9090:8080
curl http://localhost:9090/healthz

Step 3: Cut Over Traffic

Once you’re confident green is healthy, the actual cutover is a one-line change — updating the Service selector:

kubectl patch service myapp-service -p '{"spec":{"selector":{"app":"myapp","version":"green"}}}'

That’s it. Traffic now flows to green instantly, because the Service’s endpoint list is recalculated the moment the selector changes. There’s no pod churn, no gradual shift — just a clean switch.

Verify the endpoints updated correctly:

kubectl get endpoints myapp-service

Step 4: Rollback If Needed

This is where blue-green really earns its keep. If green starts throwing errors, rolling back is just as instant:

kubectl patch service myapp-service -p '{"spec":{"selector":{"app":"myapp","version":"blue"}}}'

Blue never stopped running, so this recovers your service in seconds, not minutes.

Cleaning Up

Once you’re confident in green and blue is no longer needed, scale it down or delete it:

kubectl delete deployment myapp-blue

For the next release, green becomes your new “blue,” and you’ll create a fresh deployment for the following version — some teams alternate the names, others just always deploy to a “new” and “old” label pair.

Automating This with a Script or CI/CD

Doing this by hand works for learning, but in a real pipeline you’ll want this scripted. A simple approach in a CI/CD job (GitHub Actions, GitLab CI, Jenkins) looks like this:

#!/bin/bash
set -e
NEW_VERSION=$1
kubectl apply -f green-deployment.yaml
kubectl rollout status deployment/myapp-green --timeout=120s
kubectl run smoke-test --rm -i --restart=Never --image=curlimages/curl -- \
  curl -f http://myapp-green-internal/healthz
kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'

Tools like Argo Rollouts also support blue-green as a first-class strategy, handling the label swapping and even automated analysis for you:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp
spec:
  strategy:
    blueGreen:
      activeService: myapp-active
      previewService: myapp-preview
      autoPromotionEnabled: false

I’d recommend Argo Rollouts once you’re running this pattern often enough that manual selector patches feel repetitive.

Using Ingress for Blue-Green at the Edge

If you’re routing external traffic through an Ingress controller (like NGINX or Traefik), you can do the same trick one layer up by pointing the Ingress backend at different Services, or using weighted routing annotations for a more gradual cutover:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "100"
spec:
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp-green-service
                port:
                  number: 80

Common Mistakes I’ve Seen

  • Skipping readiness probes. Without them, the Service may route traffic to pods that aren’t actually ready to serve requests.
  • Sharing a database schema incompatibly. If green needs a new column that blue’s code doesn’t expect, you can break the old version during the overlap window. Always make schema changes backward-compatible first.
  • Forgetting to scale down blue. Leaving both environments running indefinitely doubles your resource costs for no reason.
  • Not testing the rollback path. Teams often test the forward cutover but never actually rehearse flipping back — then panic when they need to.

Security and Resource Considerations

Because both versions run simultaneously during the transition, make sure your namespace has adequate ResourceQuotas and that your nodes can handle double the pod count temporarily. From a security angle, apply the same NetworkPolicies and RBAC rules to both blue and green Deployments — it’s easy to forget to update policies that reference specific label selectors.

Blue-Green vs. Canary vs. Rolling: Choosing the Right Strategy

I get asked fairly often whether blue-green is “better” than a rolling update or a canary release, and the honest answer is that they solve different problems. Rolling updates are resource-efficient and require no extra infrastructure, but they mix old and new versions during the transition, which can be risky if your new version isn’t backward compatible with the old one at the data layer. Canary releases send a small percentage of real traffic to the new version first, giving you production signal before a full rollout, but they require more sophisticated traffic-splitting infrastructure and take longer to fully promote. Blue-green sits in between: it avoids the mixed-version window entirely (every request goes to exactly one version), and the cutover is instantaneous, but it costs double the resources during the transition and doesn’t give you gradual, real-traffic validation the way a canary does. In practice, I’ve found blue-green works best for services where a bad deploy is expensive to have live even briefly — payment processing, authentication, anything where “10% of users hit a bug for two minutes” isn’t an acceptable risk profile the way it might be for an internal admin tool.

High Availability During the Cutover Window

One detail that’s easy to overlook: during the brief period between deploying green and cutting traffic over, you should still be thinking about availability within each color, not just between them. If your green Deployment only has a single replica while you’re testing it, a Pod crash during validation gives you a false read on stability. I always run the exact same replica count, resource requests, and anti-affinity rules on green as blue, so the environment you’re testing is a faithful stand-in for what will actually take production traffic. It’s also worth running your synthetic smoke tests against green through the same code path real traffic will use — hitting the Pod directly via port-forward is fine for a quick sanity check, but before flipping the Service selector, I like to route a temporary test client through an internal-only Service pointed at green specifically, so DNS resolution, readiness gating, and load balancing all get exercised exactly as they would in production.

Database and Schema Compatibility in Practice

The most common way I’ve seen blue-green deployments go wrong isn’t Kubernetes-related at all — it’s a database migration that isn’t backward compatible. If green’s code expects a new column that doesn’t exist yet, or blue’s code breaks because a column it relies on was dropped, the overlap window (how ever brief) between deploying the migration and cutting traffic becomes a landmine. The pattern I use is the “expand and contract” approach: first deploy a migration that only adds new schema elements (nullable columns, new tables) without removing anything the old code needs, deploy green against that expanded schema, cut traffic over, confirm stability, and only then run a second migration that removes anything blue depended on but green no longer needs. This turns one risky migration into two safe ones, and it’s worth the extra step for anything beyond a purely additive schema change.

Real-World CI/CD Pipeline Example

Here’s a more complete GitHub Actions workflow that ties the whole pattern together, including automated smoke testing before promotion:

name: blue-green-deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Configure kubectl
        run: aws eks update-kubeconfig --name production-cluster
      - name: Deploy green
        run: |
          sed "s/IMAGE_TAG/${{ github.sha }}/" green-deployment.yaml.tpl > green-deployment.yaml
          kubectl apply -f green-deployment.yaml
          kubectl rollout status deployment/myapp-green --timeout=180s
      - name: Smoke test green
        run: |
          kubectl run smoke-test --rm -i --restart=Never --image=curlimages/curl -- \
            curl -f http://myapp-green-internal/healthz
      - name: Cut over traffic
        run: kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'
      - name: Verify production traffic
        run: |
          sleep 10
          kubectl run verify --rm -i --restart=Never --image=curlimages/curl -- \
            curl -f http://myapp-service/healthz

This pipeline treats the smoke test as a hard gate — if it fails, traffic never cuts over, and blue keeps serving unaffected.

Summary

Blue-green deployment gives you one of the safest release strategies available in Kubernetes: two full environments, an instant switch, and an instant rollback. It costs more in resources than a rolling update, but for services where downtime or partial failure is expensive, that tradeoff is usually worth it. Start with manual Service selector patches to understand the mechanics, then graduate to a tool like Argo Rollouts once the process becomes routine.

References

Total
1
Shares

Leave a Reply

Previous Post
How to Debug Pods in Kubernetes

How to Debug Pods in Kubernetes

Next Post
How to Set Up Custom Metrics in Kubernetes

How to Set Up Custom Metrics in Kubernetes

Related Posts