Back to DevOps & SRE

Module 2: CI/CD Pipelines

Automate your software delivery with Continuous Integration and Continuous Deployment pipelines

🎯 Understanding CI/CD

CI/CD is the backbone of modern DevOps practices. It automates the process of integrating code changes, testing them, and deploying to production - enabling teams to deliver software faster and more reliably.

What is Continuous Integration (CI)?

Continuous Integration is the practice of automatically building and testing code every time a team member commits changes to version control. The goal is to detect integration issues early and fix them quickly.

CI Workflow:

1
Developer commits code to version control (Git)
2
CI server detects the change and triggers a build
3
Code is compiled/built in a clean environment
4
Automated tests run (unit, integration, linting)
5
Developer gets immediate feedback (pass/fail)

What is Continuous Deployment (CD)?

Continuous Deployment extends CI by automatically deploying every change that passes all tests to production. Continuous Delivery is similar but requires manual approval before production deployment.

Continuous Delivery

Code is always in a deployable state

  • • Automated testing and staging
  • • Manual approval for production
  • • Lower risk, more control
  • • Good for regulated industries

Continuous Deployment

Fully automated to production

  • • No manual intervention
  • • Fastest time to market
  • • Requires high test coverage
  • • Needs robust monitoring

🌿 Git Workflows

Your Git workflow determines how code flows from development to production. Different workflows suit different team sizes and release cadences.

GitFlow

A structured workflow with multiple long-lived branches. Good for scheduled releases and maintaining multiple versions.

Branch Structure:

main - Production-ready code

develop - Integration branch for features

feature/* - New features (branch from develop)

release/* - Release preparation

hotfix/* - Emergency production fixes

Best for:

Teams with scheduled releases, multiple versions in production, or complex release processes

Trunk-Based Development

Everyone commits to a single branch (trunk/main) frequently. Short-lived feature branches merge daily. Enables continuous deployment.

Key Principles:

  • • All developers work on main branch
  • • Feature branches live less than 24 hours
  • • Commit to main multiple times per day
  • • Use feature flags for incomplete features
  • • Requires strong automated testing

Best for:

High-performing teams doing continuous deployment, SaaS products, teams wanting fast feedback

GitHub Flow

Simplified workflow with main branch and feature branches. Pull requests for code review. Deploy from main branch.

Workflow Steps:

1. Create feature branch from main

2. Make commits and push regularly

3. Open pull request for review

4. Discuss and review code

5. Deploy to staging for testing

6. Merge to main after approval

7. Deploy main to production

Best for:

Small to medium teams, web applications, teams using GitHub, continuous deployment workflows

⚡ GitHub Actions

GitHub Actions is a CI/CD platform integrated directly into GitHub. It uses YAML files to define workflows that run on specific events (push, pull request, schedule, etc.).

Core Concepts

Workflow

Automated process defined in YAML file

Event

Trigger that starts a workflow (push, PR, etc.)

Job

Set of steps that run on the same runner

Step

Individual task (run command or action)

Example: Node.js CI Pipeline

Create .github/workflows/ci.yml:

name: CI Pipeline

# Trigger on push to main or pull requests
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        node-version: [18.x, 20.x]
    
    steps:
      # Checkout code
      - uses: actions/checkout@v4
      
      # Setup Node.js
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      
      # Install dependencies
      - name: Install dependencies
        run: npm ci
      
      # Run linting
      - name: Lint code
        run: npm run lint
      
      # Run tests
      - name: Run tests
        run: npm test
      
      # Build application
      - name: Build
        run: npm run build
      
      # Upload coverage
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      # Security audit
      - name: Run security audit
        run: npm audit --audit-level=moderate
      
      # Dependency check
      - name: Check for vulnerabilities
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

💡 What This Pipeline Does:

  • • Runs on every push to main/develop and all pull requests
  • • Tests against multiple Node.js versions (matrix strategy)
  • • Installs dependencies, lints code, runs tests, and builds
  • • Uploads test coverage to Codecov
  • • Runs security audits and vulnerability checks

🚀 Deployment Strategies

How you deploy to production matters. Different strategies offer different trade-offs between speed, safety, and complexity.

1. Rolling Deployment

Gradually replace old version with new version across servers. Update a few instances at a time.

✅ Pros:

  • • No downtime
  • • Simple to implement
  • • No extra infrastructure

❌ Cons:

  • • Two versions running simultaneously
  • • Slow rollback
  • • Harder to test

2. Blue-Green Deployment

Run two identical environments (blue and green). Deploy to inactive environment, then switch traffic.

Process:

1. Blue environment serves production traffic

2. Deploy new version to green environment

3. Test green environment thoroughly

4. Switch router/load balancer to green

5. Keep blue as backup for quick rollback

✅ Pros:

  • • Instant rollback
  • • Test in production-like env
  • • Zero downtime

❌ Cons:

  • • Requires 2x infrastructure
  • • Database migrations tricky
  • • More complex setup

3. Canary Deployment

Release to a small subset of users first, monitor, then gradually increase traffic to new version.

Process:

1. Deploy new version alongside old (e.g., 5% traffic)

2. Monitor metrics (errors, latency, business KPIs)

3. If healthy, increase to 25%, then 50%, then 100%

4. If issues detected, rollback immediately

✅ Pros:

  • • Reduced risk
  • • Real user feedback
  • • Gradual rollout

❌ Cons:

  • • Complex monitoring needed
  • • Slower deployment
  • • Requires traffic routing

4. Feature Flags

Deploy code with features turned off. Enable features gradually via configuration without redeployment.

// Example with LaunchDarkly
import { useLDClient } from 'launchdarkly-react-client-sdk';

function NewFeature() {
  const ldClient = useLDClient();
  const showNewUI = ldClient?.variation('new-ui-redesign', false);
  
  if (showNewUI) {
    return <NewUIComponent />;
  }
  return <OldUIComponent />;
}

✅ Pros:

  • • Decouple deploy from release
  • • A/B testing
  • • Instant rollback
  • • Gradual rollout

❌ Cons:

  • • Code complexity
  • • Technical debt
  • • Testing challenges

🔒 Pipeline Security & Secrets Management

CI/CD pipelines often need access to sensitive data (API keys, passwords, certificates). Proper secrets management is critical for security.

Best Practices

Never commit secrets to Git

Use .gitignore and secret scanning tools

Use environment variables

Store secrets in CI/CD platform's secret store

Rotate secrets regularly

Automate rotation and use short-lived credentials

Principle of least privilege

Give pipelines only the permissions they need

Audit access logs

Monitor who accesses secrets and when

GitHub Actions Secrets Example

name: Deploy to AWS

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
      
      - name: Deploy to S3
        run: |
          aws s3 sync ./build s3://my-bucket
      
      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DIST_ID }} \
            --paths "/*"

⚠️ Security Checklist:

  • ✓ Secrets stored in GitHub Secrets (Settings → Secrets)
  • ✓ Secrets never printed in logs
  • ✓ Use OIDC for AWS instead of long-lived keys when possible
  • ✓ Limit secret access to specific branches/environments
  • ✓ Enable secret scanning in repository settings

📝 Module Summary

You've learned how to automate software delivery with CI/CD pipelines:

Core Concepts:

  • ✓ CI vs CD vs Continuous Delivery
  • ✓ Git workflows (GitFlow, Trunk-based, GitHub Flow)
  • ✓ GitHub Actions workflows
  • ✓ Pipeline stages and jobs

Deployment Strategies:

  • ✓ Rolling deployments
  • ✓ Blue-green deployments
  • ✓ Canary releases
  • ✓ Feature flags

🎯 Next Steps

Now that you can automate builds and deployments, let's learn about containerization and orchestration with Docker and Kubernetes.

Continue to Module 3: Container Orchestration →