Skip to content
Codeloom
DevOps

Zero-Downtime Deployments: Strategies and Patterns

Master zero-downtime deployment strategies including rolling updates, blue-green, canary, and feature flags. Learn practical patterns for Kubernetes and cloud platforms.

·9 min read · By Codeloom
Intermediate 16 min read

What you'll learn

  • Why traditional deployments cause downtime and how to avoid it
  • Rolling update, blue-green, and canary deployment strategies
  • Implementing each strategy in Kubernetes with working examples
  • Database migration patterns that prevent deployment failures

Prerequisites

  • Basic Kubernetes knowledge (Deployments, Services)
  • Experience deploying web applications
  • Understanding of load balancers and health checks

Why Deployments Cause Downtime

A naive deployment process goes like this: stop the old version, start the new version. During the gap between stopping and starting, your service is unavailable. Even if the gap is only a few seconds, that translates to failed requests, broken user sessions, and lost revenue.

Zero-downtime deployment means releasing new versions without any interruption to users. Every request is served, old or new, with no errors during the transition. Achieving this requires understanding several strategies and choosing the right one for your situation.

Prerequisites for Zero-Downtime

Before diving into strategies, your application must meet certain requirements:

Health checks: Your application must expose a health endpoint that returns the true readiness state. Load balancers and orchestrators use this to know when a new instance is ready to receive traffic.

# Kubernetes readiness and liveness probes
readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 3

livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 10

Graceful shutdown: When an instance receives a termination signal, it must finish in-flight requests before exiting rather than dropping them immediately.

# Kubernetes lifecycle hook for graceful shutdown
lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 10"]
terminationGracePeriodSeconds: 30

Backward compatibility: The new version must work alongside the old version during the transition period. This means APIs must be backward-compatible and database schemas must support both versions simultaneously.

Strategy 1: Rolling Updates

Rolling updates gradually replace old instances with new ones. At any point during the deployment, some instances run the old version and some run the new version.

How It Works

  1. Start a new pod with the updated version.
  2. Wait for it to pass health checks.
  3. Route traffic to the new pod.
  4. Terminate one old pod.
  5. Repeat until all pods are running the new version.

Kubernetes Implementation

Rolling updates are the default strategy in Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: production
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: myregistry/api:v2.1.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi

Key settings:

  • maxSurge: 1 allows one extra pod during the update, so capacity goes from 5 to 6 temporarily.
  • maxUnavailable: 0 ensures all 5 pods remain available throughout. No pod is removed until a new one is ready.

Deploy and monitor:

# Trigger the rolling update
kubectl set image deployment/api api=myregistry/api:v2.2.0 -n production

# Watch the rollout progress
kubectl rollout status deployment/api -n production

# If something goes wrong, roll back
kubectl rollout undo deployment/api -n production

Pros and Cons

Rolling updates are simple and resource-efficient because you only need one extra pod at a time. However, during the rollout, users may be served by either the old or new version, which can cause issues if the versions behave differently. Rollback is also slow because it requires another full rolling update in reverse.

Strategy 2: Blue-Green Deployment

Blue-green deployment runs two identical production environments. The “blue” environment runs the current version, and the “green” environment runs the new version. Once green is verified, you switch traffic from blue to green instantly.

How It Works

  1. Blue is live, serving all traffic.
  2. Deploy the new version to green.
  3. Run smoke tests and validation against green.
  4. Switch the load balancer or service to point to green.
  5. Green is now live. Blue remains as a rollback target.

Kubernetes Implementation

Use two Deployments and switch the Service selector:

# Blue deployment (current version)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-blue
  namespace: production
spec:
  replicas: 5
  selector:
    matchLabels:
      app: api
      version: blue
  template:
    metadata:
      labels:
        app: api
        version: blue
    spec:
      containers:
        - name: api
          image: myregistry/api:v2.1.0
          ports:
            - containerPort: 8080
---
# Green deployment (new version)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-green
  namespace: production
spec:
  replicas: 5
  selector:
    matchLabels:
      app: api
      version: green
  template:
    metadata:
      labels:
        app: api
        version: green
    spec:
      containers:
        - name: api
          image: myregistry/api:v2.2.0
          ports:
            - containerPort: 8080

The Service initially points to blue:

apiVersion: v1
kind: Service
metadata:
  name: api
  namespace: production
spec:
  selector:
    app: api
    version: blue  # Change to "green" to switch
  ports:
    - port: 80
      targetPort: 8080

Switch traffic by updating the selector:

# Verify green is healthy
kubectl get pods -l version=green -n production

# Switch traffic to green
kubectl patch service api -n production \
  -p '{"spec":{"selector":{"version":"green"}}}'

# If something goes wrong, switch back to blue
kubectl patch service api -n production \
  -p '{"spec":{"selector":{"version":"blue"}}}'

Pros and Cons

Blue-green deployments provide instant rollback (just switch the selector back) and eliminate the mixed-version problem of rolling updates. The downside is cost: you need double the resources during deployment. For large services, this can be significant.

Strategy 3: Canary Deployment

Canary deployment routes a small percentage of traffic to the new version first. If metrics look good, you gradually increase the percentage until the new version handles all traffic.

How It Works

  1. Deploy the new version alongside the current version.
  2. Route 5% of traffic to the new version.
  3. Monitor error rates, latency, and business metrics.
  4. If healthy, increase to 25%, 50%, and finally 100%.
  5. If problems appear at any stage, route all traffic back to the old version.

Kubernetes with Nginx Ingress

Use weighted routing with Nginx Ingress:

# Stable deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-stable
spec:
  replicas: 5
  selector:
    matchLabels:
      app: api
      track: stable
  template:
    metadata:
      labels:
        app: api
        track: stable
    spec:
      containers:
        - name: api
          image: myregistry/api:v2.1.0
---
# Canary deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api
      track: canary
  template:
    metadata:
      labels:
        app: api
        track: canary
    spec:
      containers:
        - name: api
          image: myregistry/api:v2.2.0
# Stable ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-stable
  annotations:
    nginx.ingress.kubernetes.io/canary: "false"
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-stable
                port:
                  number: 80
---
# Canary ingress with weight
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-canary
                port:
                  number: 80

Increase the canary weight gradually:

# Increase canary to 25%
kubectl annotate ingress api-canary \
  nginx.ingress.kubernetes.io/canary-weight="25" --overwrite -n production

# Increase to 50%
kubectl annotate ingress api-canary \
  nginx.ingress.kubernetes.io/canary-weight="50" --overwrite -n production

# Full rollout: remove canary, update stable
kubectl annotate ingress api-canary \
  nginx.ingress.kubernetes.io/canary-weight="100" --overwrite -n production

Automated Canary with Argo Rollouts

For automated canary analysis, use Argo Rollouts:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: api
spec:
  replicas: 5
  strategy:
    canary:
      steps:
        - setWeight: 5
        - pause: { duration: 5m }
        - setWeight: 25
        - pause: { duration: 10m }
        - setWeight: 50
        - pause: { duration: 10m }
        - setWeight: 100
      analysis:
        templates:
          - templateName: success-rate
        startingStep: 1
      canaryService: api-canary
      stableService: api-stable
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: myregistry/api:v2.2.0
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  metrics:
    - name: success-rate
      interval: 60s
      successCondition: result[0] >= 0.99
      provider:
        prometheus:
          address: http://prometheus:9090
          query: |
            sum(rate(http_requests_total{app="api",status!~"5.."}[5m]))
            /
            sum(rate(http_requests_total{app="api"}[5m]))

Argo Rollouts automatically promotes or rolls back the canary based on the analysis template. If the success rate drops below 99%, the rollout is aborted and all traffic returns to the stable version.

Database Migration Patterns

Database changes are the hardest part of zero-downtime deployments. You cannot simply add a required column or rename a table while both versions of your application are running.

The Expand-Contract Pattern

Break schema changes into backward-compatible steps:

Step 1: Expand - Add the new column as nullable or with a default value.

-- Deploy 1: Add column (backward compatible)
ALTER TABLE orders ADD COLUMN shipping_method VARCHAR(50) DEFAULT 'standard';

Step 2: Migrate - Backfill existing data and update the application to write to both old and new columns.

-- Deploy 2: Backfill data
UPDATE orders SET shipping_method = 'standard' WHERE shipping_method IS NULL;

Step 3: Contract - Once all application instances use the new column, remove the old one.

-- Deploy 3: Drop old column (only after all instances are updated)
ALTER TABLE orders DROP COLUMN legacy_shipping_type;

Rules for Safe Migrations

  • Never rename a column in a single step. Add the new column, migrate data, update the app, then drop the old column.
  • Never add a NOT NULL constraint without a default value.
  • Never drop a column that the current running version still reads from.
  • Always test migrations against a copy of production data before applying them.
  • Use migration tools that support reversible migrations (Flyway, Alembic, Rails Migrations).

Choosing the Right Strategy

StrategyRollback SpeedResource CostComplexityBest For
Rolling UpdateSlow (minutes)Low (+1 pod)LowStandard deployments, stateless services
Blue-GreenInstantHigh (2x)MediumCritical services requiring instant rollback
CanaryFast (seconds)MediumHighHigh-traffic services, risky changes

For most teams, rolling updates are the starting point. Graduate to canary deployments for critical services where you want to validate changes with real traffic before full rollout. Use blue-green when you need the ability to switch back instantly, such as major version upgrades.

Wrapping Up

Zero-downtime deployments are not a luxury; they are an expectation for modern applications. Start with rolling updates and proper health checks, which Kubernetes provides out of the box. Add graceful shutdown handling and backward-compatible database migrations to eliminate the most common sources of deployment-related downtime. As your deployment confidence grows, adopt canary deployments with automated analysis to catch issues before they reach all users. The investment in zero-downtime patterns pays dividends in reliability, developer confidence, and the ability to deploy more frequently with less risk.