CI/CD Pipeline Design Guide 2026: From Commit to Production in 10 Minutes

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

CI/CD 2026: Ship Fast, Break Nothing

A good CI/CD pipeline takes you from code commit to production in 10 minutes with zero downtime. Here's how to build one.

The Pipeline Stages

CommitLint/FormatTestsBuildDockerStagingE2EProduction
  10s      30s          2min    2min    3min     1min     3min    1min

Total: ~12 minutes commit to production

Complete Pipeline

# .github/workflows/pipeline.yml
name: Production Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # Stage 1: Quality checks (parallel)
  quality:
    name: Code Quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm run format:check

  # Stage 2: Tests (parallel with quality)
  test:
    name: Tests
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env: { POSTGRES_PASSWORD: test, POSTGRES_DB: testdb }
        options: --health-cmd pg_isready --health-interval 5s
        ports: ['5432:5432']
      redis:
        image: redis:7-alpine
        ports: ['6379:6379']

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm run db:migrate
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/testdb
      - run: npm run test:ci
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379
      - uses: codecov/codecov-action@v4
        with: { token: ${{ secrets.CODECOV_TOKEN }} }

  # Stage 3: Build Docker image
  build:
    name: Build Image
    runs-on: ubuntu-latest
    needs: [quality, test]
    outputs:
      image: ${{ steps.meta.outputs.tags }}
      digest: ${{ steps.build.outputs.digest }}

    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=sha-
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}

      - id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # Stage 4: Deploy to staging
  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: build
    environment:
      name: staging
      url: https://staging.webcoderspeed.com
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4

      - name: Deploy to staging
        run: |
          # Update staging with new image
          kubectl set image deployment/app-staging \
            app=${{ needs.build.outputs.image }} \
            --namespace=staging
          kubectl rollout status deployment/app-staging --namespace=staging

  # Stage 5: E2E tests on staging
  e2e:
    name: E2E on Staging
    runs-on: ubuntu-latest
    needs: deploy-staging
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
        env:
          BASE_URL: https://staging.webcoderspeed.com
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

  # Stage 6: Production deploy
  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: e2e
    environment:
      name: production
      url: https://webcoderspeed.com

    steps:
      - name: Blue-green deploy
        run: |
          # Switch traffic to green (new version)
          kubectl apply -f k8s/production/
          kubectl rollout status deployment/app-production

Blue-Green Deployment

# Zero-downtime deployment strategy
# Blue: current production
# Green: new version

# k8s/service.yaml — switch traffic by updating selector
apiVersion: v1
kind: Service
metadata:
  name: app-production
spec:
  selector:
    app: webcoderspeed
    version: blue  # Switch to 'green' for deploy

# Deploy green, then switch service selector
kubectl apply -f k8s/deployment-green.yaml
kubectl rollout status deployment/app-green
kubectl patch service app-production -p '{"spec":{"selector":{"version":"green"}}}'

# Quick rollback if needed
kubectl patch service app-production -p '{"spec":{"selector":{"version":"blue"}}}'

Canary Release

# Send 10% of traffic to new version

# k8s/canary-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"  # 10% traffic
spec:
  rules:
    - host: webcoderspeed.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app-canary-service
                port: { number: 80 }

Deployment Notifications

  notify:
    name: Notify
    needs: [deploy-production]
    runs-on: ubuntu-latest
    if: always()
    steps:
      - name: Slack notification
        uses: slackapi/slack-github-action@v1
        with:
          channel-id: '#deployments'
          slack-message: |
            *${{ job.status == 'success' && '✅' || '❌' }} Deployment ${{ job.status }}*
            Version: `${{ github.sha }}`
            By: ${{ github.actor }}
            ${{ job.status == 'success' && 'Production is live!' || 'Check GitHub Actions for details' }}
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

Rollback Strategy

#!/bin/bash
# rollback.sh — Emergency rollback script

PREVIOUS_VERSION=$(kubectl rollout history deployment/app-production | tail -3 | head -1 | awk '{print $1}')

echo "Rolling back to revision $PREVIOUS_VERSION..."
kubectl rollout undo deployment/app-production

# Or to specific revision
kubectl rollout undo deployment/app-production --to-revision=5

echo "Rollback complete!"
kubectl rollout status deployment/app-production

Deployment Metrics to Track

MetricTargetAlert Threshold
Deployment frequencyMultiple per day< 1 per week
Lead time (commit → prod)< 15 minutes> 1 hour
Change failure rate< 5%> 15%
Recovery time< 30 minutes> 2 hours

These are the DORA metrics — the industry standard for measuring engineering team performance. A great CI/CD pipeline is the foundation of high performance.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro