Kubernetes deployments aren’t just about getting new code out; they’re about managing risk and ensuring availability.

Let’s see what this looks like in practice. Imagine we have a simple Nginx deployment serving a static index.html.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.0
        ports:
        - containerPort: 80

And a Service to expose it:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer

When we kubectl apply this, Kubernetes spins up 3 pods matching the app: my-app label and a LoadBalancer service directs traffic to them.

Rolling Update

This is the default strategy in Kubernetes. When you update the image tag in your Deployment spec, Kubernetes gradually replaces old pods with new ones. It ensures that at least maxUnavailable pods are down at any given time and that the number of running pods doesn’t exceed maxSurge above the desired replicas.

Let’s say we want to update to nginx:1.22.0. We’d change the image field in the Deployment manifest:

# ...
      containers:
      - name: nginx
        image: nginx:1.22.0 # <-- Changed here
# ...

And then run kubectl apply -f deployment.yaml.

Kubernetes will start a new pod with nginx:1.22.0. Once it’s ready, it will terminate an old pod with nginx:1.21.0. This continues until all old pods are replaced.

The key levers here are spec.strategy.rollingUpdate.maxUnavailable and spec.strategy.rollingUpdate.maxSurge. If replicas: 3, maxUnavailable: 1, and maxSurge: 1, Kubernetes will ensure at least 2 pods are running and at most 4 pods are running during the update. The old pod is only terminated after the new pod is deemed ready by its readiness probe.

Blue-Green Deployment

This strategy involves running two identical environments, "Blue" (current version) and "Green" (new version). Traffic is directed to one environment. Once the Green environment is ready, traffic is switched over. This allows for instant rollback if issues arise.

In Kubernetes, this is often implemented using two Deployments and a single Service.

First, our initial Blue deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: blue # <-- Label to distinguish
  template:
    metadata:
      labels:
        app: my-app
        version: blue
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.0
        ports:
        - containerPort: 80

And the Service pointing to the Blue version:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app
    version: blue # <-- Selector for Blue
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer

Now, to deploy the new version (Green), we create a new Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: green # <-- Label for Green
  template:
    metadata:
      labels:
        app: my-app
        version: green
    spec:
      containers:
      - name: nginx
        image: nginx:1.22.0 # <-- New image
        ports:
        - containerPort: 80

Once my-app-green is deployed and ready, we switch traffic by updating the Service’s selector:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app
    version: green # <-- Now points to Green
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer

Rollback is as simple as changing the Service selector back to version: blue.

Canary Release

Canary releases gradually roll out new versions to a small subset of users before a full rollout. This allows for testing in production with minimal blast radius.

This is typically achieved by using multiple Deployments or a single Deployment with weighted routing via an Ingress controller or a service mesh.

Let’s use two Deployments, similar to Blue-Green, but with a twist in traffic management.

Initial Blue Deployment (same as before):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-blue
spec:
  replicas: 10 # More replicas for broader initial audience
  selector:
    matchLabels:
      app: my-app
      version: blue
  template:
    metadata:
      labels:
        app: my-app
        version: blue
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.0
        ports:
        - containerPort: 80

Canary Green Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-green-canary
spec:
  replicas: 1 # Small number for initial canary
  selector:
    matchLabels:
      app: my-app
      version: green-canary
  template:
    metadata:
      labels:
        app: my-app
        version: green-canary
    spec:
      containers:
      - name: nginx
        image: nginx:1.22.0
        ports:
        - containerPort: 80

The critical piece is how traffic is routed. You’d use an Ingress controller (like Nginx Ingress, Traefik, or HAProxy Ingress) or a service mesh (like Istio or Linkerd) to split traffic.

For example, with Nginx Ingress, you might have an Ingress resource that looks like this:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10" # 10% of traffic goes to canary
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-service-blue # Service for blue
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-service-green-canary # Service for canary
            port:
              number: 80
---
# Service for blue
apiVersion: v1
kind: Service
metadata:
  name: my-app-service-blue
spec:
  selector:
    app: my-app
    version: blue
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
# Service for canary
apiVersion: v1
kind: Service
metadata:
  name: my-app-service-green-canary
spec:
  selector:
    app: my-app
    version: green-canary
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

The canary-weight: "10" annotation tells Nginx Ingress to send 10% of traffic to my-app-service-green-canary and 90% to my-app-service-blue. You monitor the canary version, and if it’s stable, you increase the weight or promote it by shifting all traffic.

The real magic of canary deployments is often hidden in the traffic management layer, which can dynamically adjust percentages, perform A/B testing, or even route specific user headers to the canary.

The next step in advanced deployment strategies is often automating the monitoring and promotion/rollback based on metrics.

Want structured learning?

Take the full Containers & Kubernetes course →