G
GuideDevOps
Lesson 2 of 11

CI vs CD Explained

Part of the CI/CD Pipelines tutorial series.

The Three Legs of CI/CD

Often confused because they use similar acronyms, but each serves a completely different purpose:


1. Continuous Integration (CI)

Definition: Developers constantly merge code into a shared mainbranch. Each merge automatically runs build, test, and validation steps.

How CI Works

Developer commits to feature-branch
         ↓
GitHub/GitLab notifies CI system (webhook)
         ↓
CI system pulls code
         ↓
Run linting checks (2 sec)
         ↓
Run unit tests (30 sec) ← If FAILS, STOP here, notify dev
         ↓
Build artifact (60 sec)
         ↓
Report results back on GitHub PR
         ↓
Developer can merge if all green ✅

Goal of CI

✅ Catch bugs EARLY (in minutes, not weeks) ✅ Enable frequent merges (10+ per day without conflicts) ✅ Provide rapid feedback (developer still in context) ✅ Prevent "merge hell" (too many branches diverging) ✅ Enforce code quality standards

Real-World CI Example

# .github/workflows/ci.yml
name: CI Pipeline
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
 
jobs:
  quality-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint
        run: npm run lint
        
      - name: Run unit tests
        run: npm test -- --coverage
        
      - name: Check code coverage
        run: |
          COVERAGE=$(npm run coverage:report | grep "Statements:" | awk '{print $NF}' | sed 's/%//')
          if (( $(echo "$COVERAGE < 80" | bc -l) )); then
            echo "Coverage too low: $COVERAGE%"
            exit 1
          fi
          
      - name: Build project
        run: npm run build
        
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3

CI Pipeline Timeline

10:00:00 - Developer pushes to GitHub
10:00:15 - ESLint runs → PASS ✅
10:00:45 - Unit tests run (45 tests) → PASS ✅
10:01:15 - Coverage check (85%) → PASS ✅
10:02:00 - Build → SUCCESS ✅
10:02:30 - All checks green! PR approved for merge ✅

Total time: 2.5 minutes

When to Use CI Only

  • Small teams (fewer than 5 developers)
  • Learning projects
  • High safety requirements (medical industry)
  • Very risk-averse organizations

Not recommended for production releases (someone still must manually deploy)


2. Continuous Delivery (CD - with Manual Gate)

Definition: Every commit that passes CI is automatically built, tested, and packaged into a release artifact. But actual deployment to production requires manual approval.

How Continuous Delivery Works

Commit passes all CI tests ✅
         ↓
Automatically deploy to staging ✅
         ↓
Run full integration tests ✅
         ↓
Run end-to-end tests (real browser, real DB) ✅
         ↓
Run performance tests ✅
         ↓
Package as Docker image with version tag ✅
         ↓
Create release in GitHub/GitLab ✅
         ↓
👤 WAIT FOR HUMAN APPROVAL 👤
[PM/Manager clicks "Deploy to Production" button]
         ↓
Automatically deploy to production ✅
         ↓
Run smoke tests against production ✅
         ↓
Monitoring & alerts active ✅

Goal of Continuous Delivery

✅ Always have production-ready code in staging ✅ Humans control WHEN to release (business timing) ✅ Safety gate for critical decisions ✅ Traceability (know who deployed what) ✅ Quick rollback capability (seconds) ✅ Balances speed with control

Real-World Continuous Delivery Example

# .github/workflows/cd.yml
name: Continuous Delivery
on:
  push:
    branches: [main]
    tags: ['v*']
 
jobs:
  build-and-test:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run lint
      - run: npm test
      - run: npm run build
      
      - name: Build Docker image
        run: |
          docker build -t myapp:${{ github.sha }} .
          docker build -t myapp:latest .
      
      - name: Push to registry
        run: |
          docker push myapp:${{ github.sha }}
          docker push myapp:latest
 
  deploy-staging:
    needs: build-and-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to Staging
        run: |
          kubectl set image deployment/myapp-staging \
            myapp=myapp:${{ github.sha }} \
            -n staging
      
      - name: Wait for rollout
        run: kubectl rollout status deployment/myapp-staging -n staging
      
      - name: Run integration tests
        run: npm run test:integration -- --url https://staging.myapp.com
      
      - name: Run E2E tests
        run: npm run test:e2e -- --url https://staging.myapp.com
      
      - name: Run performance tests
        run: npm run test:performance -- --url https://staging.myapp.com
 
  manual-approval:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
    steps:
      - run: echo "Awaiting GitHub approval..."
 
  deploy-production:
    needs: manual-approval
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to Production
        run: |
          kubectl set image deployment/myapp \
            myapp=myapp:${{ github.sha }} \
            -n production
      
      - name: Monitor deployment
        run: |
          kubectl rollout status deployment/myapp -n production
          
      - name: Health checks
        run: |
          curl -f https://myapp.com/health || exit 1
          
      - name: Create GitHub Release
        run: |
          gh release create v${{ github.sha }} \
            --notes "Production deployment" \
            --draft=false
 
  notify:
    needs: [deploy-staging, deploy-production]
    runs-on: ubuntu-latest
    if: always()
    steps:
      - name: Send Slack notification
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
            -d '{"text":"Production deployment complete ✅"}'

When to Use Continuous Delivery

Regulated Industries: Banking, insurance, healthcare ✅ Marketing Timing: Coordinated product launch ✅ Business-Critical: Can't afford mistakes ✅ Organizational Policy: Compliance requirements ✅ Most companies - 80% use this model


3. Continuous Deployment (CD - Fully Automatic)

Definition: Every commit that passes all tests is automatically deployed to production. No manual gate, no waiting.

How Continuous Deployment Works

Commit passes all tests ✅
         ↓
Deploy to production automatically ✅
         ↓
Run smoke tests against prod ✅
         ↓
Monitor error rates & latency ✅
         ↓
If all green: DONE ✅
If errors detected: Auto-rollback 🔄

Goal of Continuous Deployment

⚡ Maximum speed - features ship in minutes ⚡ Fastest feedback loop - real users test immediately ⚡ Smaller changes = lower risk ⚡ Auto-rollback handles failures automatically ⚡ Highest deployment frequency (10+ per day)

Who Uses Continuous Deployment

🎬 Netflix: ~1,000 deployments/day to production 💬 Slack: Multiple deployments/day 🛍️ Etsy: 50+ deployments/day 🌩️ Amazon: 30,000+ internal deployments/day 🔍 Google: 100,000+ deployments/day

Real-World Continuous Deployment Example

jobs:
  auto-deploy-production:
    runs-on: ubuntu-latest
    needs: build-and-test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to Production
        run: |
          kubectl set image deployment/myapp \
            myapp=myapp:${{ github.sha }} \
            -n production
      
      - name: Monitor deployment rollout
        run: kubectl rollout status deployment/myapp -n production
      
      - name: Wait 2 minutes for stabilization
        run: sleep 120
      
      - name: Check error rate (CloudWatch)
        run: |
          ERROR_RATE=$(aws cloudwatch get-metric-statistics \
            --namespace AWS/ApplicationELB \
            --metric-name HTTPCode_Target_5XX_Count \
            --start-time $(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%S) \
            --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
            --period 60 \
            --statistics Sum \
            --query 'Datapoints[0].Sum' \
            --output text)
          
          if [ "$ERROR_RATE" != "None" ] && [ "$ERROR_RATE" -gt 100 ]; then
            echo "Error rate too high: $ERROR_RATE 5XX errors in last 5 min"
            kubectl rollout undo deployment/myapp -n production
            exit 1
          fi
      
      - name: Check latency
        run: |
          LATENCY=$(aws cloudwatch get-metric-statistics \
            --namespace AWS/ApplicationELB \
            --metric-name TargetResponseTime \
            --start-time $(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%S) \
            --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
            --period 60 \
            --statistics Average \
            --query 'Datapoints[0].Average' \
            --output text)
          
          if (( $(echo "$LATENCY > 2.0" | bc -l) )); then
            echo "Latency too high: ${LATENCY}s (max: 2.0s)"
            kubectl rollout undo deployment/myapp -n production
            exit 1
          fi
      
      - name: Deployment successful
        if: success()
        run: echo "Production deployment successful ✅"
      
      - name: Send Slack notification
        if: always()
        run: |
          STATUS=${{ job.status }}
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
            -d "{\"text\":\"Production deployment: $STATUS\"}"

Requirements for Continuous Deployment

Without these, don't use continuous deployment:

  1. Automated Testing: 90%+ coverage required
  2. Monitoring & Alerts: Must detect issues in under 2 minutes
  3. Auto-Rollback: System must rollback without human intervention
  4. Feature Flags: Deploy code but feature OFF for users
  5. Load Tests: Must verify performance before release
  6. Team Discipline: Code reviews must be tight
  7. Experience: Team must understand risks

Comparison Matrix

AspectCI OnlyContinuous DeliveryContinuous Deployment
TestingAutomatedAutomatedAutomated + monitoring
DeploymentManualManual approval gateAutomatic
DecisionDevelopersHumans (PM/Release)Algorithm (metrics)
RiskMediumLowVery Low (auto-rollback)
SpeedSlow (waiting for merge)Fast (1-2x/day)Fastest (10+x/day)
SafetyRequires disciplineRequires approvalRequires monitoring
Best ForTeams learning CIMost companiesTech-forward, high-scale
ExamplesGitHub teamsBanks, insurance, retailNetflix, Etsy, Slack, Uber
Adoption30%60%10%

The Deployment Spectrum

No Automation     CI Only          Continuous        Continuous
(Manual)          (Test Only)      Delivery          Deployment
   |________________|__________________|_________________|
 Risky            Growing            Balanced           Fast
 Slow             Safe               Safe & Fast        Requires expertise

Which Should YOU Use?

Use CI Only if:

  • ❌ Very small team (fewer than 5 developers)
  • ❌ Learning project
  • ❌ Hobby/personal project
  • Better to start here and graduate to Continuous Delivery

Use Continuous Delivery if:

  • ✅ Medium to large team
  • ✅ Business/marketing timing matters
  • ✅ Regulated industry (banking, healthcare)
  • ✅ Risk-averse organization
  • This is what 60% of companies use
  • RECOMMENDED for most situations

Use Continuous Deployment if:

  • ✅ Highly experienced team
  • ✅ Fast feedback is critical to business
  • ✅ Strong monitoring and alerting in place
  • ✅ Feature flags implemented
  • ✅ Auto-rollback available
  • ✅ Competitive advantage critical
  • Only use if you have all prerequisites

Pro Tip: Feature Flags Enable Safe Deployment

Use feature flags to separate deployment from release:

// Code deployed to production but feature is OFF
if (featureFlags.get('new-checkout-v2')) {
  renderNewCheckout();  // Hidden from users
} else {
  renderOldCheckout();  // Active for all users
}

Timeline:

10:00 - Deploy code with flag OFF
10:05 - Monitor metrics (no changes to users)
15:00 - Enable for 5% of users
15:30 - Monitor error rates (0.0%)
16:00 - Enable for 25% of users
16:30 - Monitor latency (2ms slower)
17:00 - Enable for 100% of users
17:30 - Monitor for issues
18:00 - All green, disable rollback safety

If anything goes wrong, just toggle the flag OFF (instant rollback!).


Decision Tree

Start here
   |
   ├─ Team size < 5 and learning?
   │  └─> CI Only
   │
   ├─ Regulated industry or risk-averse?
   │  └─> Continuous Delivery (with canary/blue-green)
   │
   ├─ Want speed but need safety?
   │  └─> Continuous Delivery (with feature flags)
   │
   └─ Netflix-scale with expert team?
      └─> Continuous Deployment (with all safety checks)