CI/CD Best Practices
03 / 03

Deployment Patterns

Deployment Patterns

Choosing the right deployment strategy reduces downtime, limits blast radius, and enables fast rollback. The strategy depends on your infrastructure, team confidence, and risk tolerance.

Blue-Green Deployment

# Blue-Green with Kubernetes and a load balancer
# Two identical deployments; switch traffic by updating the Service selector

# Deploy new version to green
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      slot: green
  template:
    metadata:
      labels:
        app: myapp
        slot: green
    spec:
      containers:
        - name: myapp
          image: myapp:v2.0.0

---
# Service currently pointing to blue
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
    slot: blue       # switch to 'green' after smoke tests
  ports:
    - port: 80
      targetPort: 3000

# Switching traffic (after green smoke tests pass):
# kubectl patch service myapp -p '{"spec":{"selector":{"slot":"green"}}}'
# Rollback (instant):
# kubectl patch service myapp -p '{"spec":{"selector":{"slot":"blue"}}}'

Canary Releases

# Canary with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp
spec:
  replicas: 10
  strategy:
    canary:
      steps:
        - setWeight: 10            # 10% traffic to canary
        - pause: { duration: 5m }  # wait 5 min, monitor metrics
        - setWeight: 30
        - pause: { duration: 10m }
        - setWeight: 60
        - pause: { duration: 10m }
        - setWeight: 100           # full rollout
      analysis:
        templates:
          - templateName: error-rate
        args:
          - name: service-name
            value: myapp

---
# AnalysisTemplate — auto-rollback if error rate > 5%
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: error-rate
spec:
  metrics:
    - name: error-rate
      interval: 1m
      successCondition: result[0] < 0.05
      failureLimit: 3
      provider:
        prometheus:
          address: http://prometheus:9090
          query: |
            sum(rate(http_requests_total{status=~"5.."}[1m]))
            / sum(rate(http_requests_total[1m]))

Feature Flags

// Feature flags — decouple deployment from release
// Tools: LaunchDarkly, Unleash, Flagsmith, GrowthBook, Vercel Edge Config

// LaunchDarkly example
import { init } from 'launchdarkly-node-server-sdk';

const client = init(process.env.LAUNCHDARKLY_SDK_KEY!);
await client.waitForInitialization();

// Evaluate a flag for a user
const context = { kind: 'user', key: user.id, email: user.email, plan: 'pro' };
const showNewDashboard = await client.variation('new-dashboard', context, false);

if (showNewDashboard) {
  // Show new feature to % of users
}

// React hook with OpenFeature (vendor-agnostic)
import { useBooleanFlagValue } from '@openfeature/react-sdk';

function Dashboard() {
  const showNewChart = useBooleanFlagValue('new-chart', false);
  return showNewChart ? <NewChart /> : <OldChart />;
}

// Kill switch pattern — emergency disable without deploy
// 1. Check flag before expensive operation
// 2. Set flag to false in dashboard to instantly disable
// 3. No code change or deployment needed
async function processPayment(order: Order) {
  const paymentsEnabled = await flags.get('payments-enabled', true);
  if (!paymentsEnabled) throw new Error('Payments temporarily disabled');
  return stripe.createCharge(order);
}

Rollback Strategies & Monitoring

# Kubernetes rollback
kubectl rollout status deployment/myapp          # check rollout health
kubectl rollout history deployment/myapp         # view revision history
kubectl rollout undo deployment/myapp            # rollback to previous revision
kubectl rollout undo deployment/myapp --to-revision=3   # rollback to specific revision

# Docker Swarm rollback
docker service update --rollback myapp

# Vercel — instant rollback to any previous deployment
vercel rollback [deployment-url]

# Database migrations — always write reversible migrations
# Forward: ALTER TABLE users ADD COLUMN feature_flags jsonb DEFAULT '{}';
# Backward: ALTER TABLE users DROP COLUMN feature_flags;

# Zero-downtime DB schema changes (expand-contract pattern):
# Step 1 — Expand: add new column (nullable, no constraint yet)
# Step 2 — Migrate: backfill data in batches
# Step 3 — Contract: add NOT NULL constraint, drop old column

# Health check endpoint — used by load balancers to detect failures
# GET /health → 200 { status: "ok", db: "ok", cache: "ok" }
# GET /ready  → 200 when ready to serve traffic (startup probe)

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free