CI/CD

Continuous Integration & Continuous Deployment: From Code to Production

CI/CD transforms software delivery from a risky, manual process into an automated, reliable pipeline. By automatically building, testing, and deploying code changes, teams can release features faster, catch bugs earlier, and deliver value to users continuously. This automation isn't just about speed—it's about creating a safety net that gives developers confidence to innovate without fear of breaking production.

Rapid Delivery

Deploy changes multiple times per day with confidence

Early Bug Detection

Catch issues in minutes, not days or weeks

Team Efficiency

Free developers from manual deployment tasks

What is CI/CD?

The Restaurant Kitchen Analogy

Imagine a busy restaurant kitchen:

Without CI/CD (Traditional Approach):

  • Chef prepares entire meal alone
  • No one tastes until it reaches the customer
  • If something’s wrong, the whole meal is remade
  • One chef = one meal at a time

With CI/CD (Modern Approach):

  • Multiple chefs work on different dishes
  • Each component is tasted immediately (CI)
  • Approved dishes go straight to customers (CD)
  • Kitchen runs continuously, serving many orders

Breaking It Down

Continuous Integration (CI): Developers merge code changes frequently (usually several times per day), with each merge triggering automated builds and tests.

Continuous Deployment (CD): Code that passes all tests is automatically deployed to production without manual intervention.

Continuous Delivery: A variation where code is automatically prepared for release but requires manual approval to deploy.

CI/CD Crash Course (30 Minutes)

Your First Pipeline

Let’s build a simple CI/CD pipeline for a Node.js application using GitHub Actions:

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

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

jobs:
  # Job 1: Continuous Integration
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run linter
      run: npm run lint
    
    - name: Run tests
      run: npm test
    
    - name: Build application
      run: npm run build

  # Job 2: Continuous Deployment
  deploy:
    needs: test  # Only run if tests pass
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Deploy to production
      run: |
        # Your deployment commands here
        echo "Deploying to production..."

Understanding the Pipeline Flow

  1. Trigger: Code pushed to main/develop or PR opened
  2. Checkout: Pipeline gets latest code
  3. Setup: Install required tools (Node.js)
  4. Dependencies: Install project dependencies
  5. Quality Checks: Run linter for code standards
  6. Tests: Execute automated tests
  7. Build: Compile/bundle the application
  8. Deploy: If on main branch and tests pass, deploy

Quick Start Checklist

  • Create .github/workflows/ directory
  • Add workflow YAML file
  • Define trigger events (push, PR, schedule)
  • Set up build environment
  • Add test commands
  • Configure deployment (if ready)
  • Commit and push to see it run!

GitHub Actions

Best for: GitHub-hosted projects, easy integration

# Example: Python app with GitHub Actions
name: Python CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with:
        python-version: '3.12'
    - run: |
        pip install -r requirements.txt
        pytest

Recent GitHub Actions Features:

  • Larger runners: Up to 64 vCPUs and 256 GB RAM
  • GPU runners: NVIDIA GPU support for ML workloads
  • Arm64 runners: Native ARM architecture support
  • Deployment protection rules: Environment-specific approvals

GitLab CI/CD

Best for: GitLab users, built-in DevOps features

# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

test:
  stage: test
  script:
    - npm install
    - npm test

deploy:
  stage: deploy
  script:
    - npm run build
    - npm run deploy
  only:
    - main

Jenkins

Best for: Complex workflows, self-hosted, plugins

// Jenkinsfile
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Deploy') {
            steps {
                sh './deploy.sh'
            }
        }
    }
}

CircleCI

Best for: Fast builds, Docker support

# .circleci/config.yml
version: 2.1
jobs:
  build-and-test:
    docker:
      - image: cimg/node:18.0
    steps:
      - checkout
      - run: npm install
      - run: npm test
      - run: npm run build

workflows:
  main:
    jobs:
      - build-and-test

Platform Comparison

Platform Pros Cons Best Use Case
GitHub Actions Free for public repos, great integration Limited free minutes for private GitHub projects
GitLab CI Complete DevOps platform Can be complex Full DevOps lifecycle
Jenkins Highly customizable, mature Requires maintenance Enterprise, complex needs
CircleCI Fast, good caching Can get expensive Speed-critical projects
Travis CI Simple setup Less popular now Legacy projects
Azure DevOps Great for .NET Microsoft-centric Windows/Azure shops
Buildkite Hybrid model, scalable Requires own runners High-security needs
Drone Container-native, simple Smaller community Kubernetes environments

Pipeline Design Patterns

1. Simple Linear Pipeline

stages:
  - build
  - test
  - deploy

Use when: Starting out, simple projects

2. Parallel Execution

test:
  parallel:
    - unit-tests:
        script: npm run test:unit
    - integration-tests:
        script: npm run test:integration
    - lint:
        script: npm run lint

Use when: Tests are independent, need speed

3. Matrix Builds

strategy:
  matrix:
    node-version: [14, 16, 18]
    os: [ubuntu-latest, windows-latest]

Use when: Testing across multiple environments

4. Fan-out/Fan-in

stages:
  - build
  - parallel-tests  # Fan-out: multiple parallel jobs
  - aggregate       # Fan-in: collect results
  - deploy

Use when: Complex test suites, need aggregated results

5. Blue-Green Pipeline

deploy-blue:
  script: deploy_to_blue_environment()
  
smoke-test:
  script: test_blue_environment()
  
switch-traffic:
  script: route_traffic_to_blue()
  
cleanup-green:
  script: teardown_green_environment()

Use when: Zero-downtime deployments critical

Testing Strategies in CI/CD

The Testing Pyramid

        /\
       /  \  E2E Tests (Slow, Few)
      /    \
     /------\ Integration Tests (Medium)
    /        \
   /----------\ Unit Tests (Fast, Many)

Unit Tests in CI

// Fast, isolated tests
describe('Calculator', () => {
  it('adds two numbers', () => {
    expect(add(2, 3)).toBe(5);
  });
});

Run on: Every commit Duration: < 5 minutes

Integration Tests

// Test component interactions
describe('API Integration', () => {
  it('creates user and sends email', async () => {
    const user = await createUser(data);
    expect(emailService.sent).toBe(true);
  });
});

Run on: Pull requests Duration: 5-15 minutes

End-to-End Tests

// Test complete user flows
describe('Checkout Flow', () => {
  it('completes purchase', async () => {
    await login();
    await addToCart();
    await checkout();
    expect(orderConfirmation).toBeVisible();
  });
});

Run on: Pre-deployment Duration: 15-60 minutes

Performance Tests

performance-test:
  script:
    - k6 run load-test.js
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Security in CI/CD

Secrets Management

Bad Practice:

deploy:
  script:
    - API_KEY="sk-1234567890" npm run deploy  # Never do this!

Good Practice:

deploy:
  script:
    - npm run deploy
  environment:
    name: production
  secrets:
    API_KEY:
      from_secret: api_key_production

Security Scanning Pipeline

security-scan:
  stage: security
  parallel:
    - dependency-check:
        script: 
          - npm audit
          - pip-audit  # Python (replaced safety)
          - bundle audit  # Ruby
          - osv-scanner --recursive .  # Google's OSV Scanner
    
    - sast:  # Static Application Security Testing
        script:
          - semgrep --config=auto
          - bandit -r src/  # Python
          - snyk code test
    
    - container-scan:
        script:
          - trivy image myapp:latest
          - grype myapp:latest  # Anchore scanner
          - docker scout cves myapp:latest  # Docker's native scanner
    
    - secrets-scan:
        script:
          - gitleaks detect --source=.
          - trufflehog filesystem . --json

Security Best Practices

  1. Rotate Secrets Regularly ```yaml
    • name: Check secret age run: | if [ $(secret_age $SECRET_NAME) -gt 90 ]; then echo “::error::Secret older than 90 days!” exit 1 fi ```
  2. Least Privilege Access
    deploy:
      permissions:
        contents: read
        deployments: write
        # Only what's needed, nothing more
    
  3. Audit Logs ```yaml after_script:
    • echo “Deployed by $CI_USER at $CI_TIMESTAMP” » audit.log ```

Deployment Strategies

1. Blue-Green Deployment

deploy-blue-green:
  steps:
    # Deploy to inactive environment
    - name: Deploy to Blue
      run: |
        kubectl set image deployment/app app=myapp:$VERSION -n blue
        kubectl wait --for=condition=ready pod -l app=myapp -n blue
    
    # Test new version
    - name: Smoke Test Blue
      run: ./smoke-test.sh https://blue.example.com
    
    # Switch traffic
    - name: Switch to Blue
      run: kubectl patch service app -p '{"spec":{"selector":{"env":"blue"}}}'
    
    # Keep green as rollback option
    - name: Tag Green for Rollback
      run: kubectl label deployment/app version=previous -n green

2. Canary Deployment

canary-deploy:
  steps:
    # Deploy canary (10% traffic)
    - name: Deploy Canary
      run: |
        kubectl apply -f canary-deployment.yaml
        kubectl set image deployment/app-canary app=myapp:$VERSION
    
    # Monitor metrics
    - name: Monitor Canary
      run: |
        for i in {1..10}; do
          ERROR_RATE=$(curl -s https://metrics.api/error-rate)
          if [ $ERROR_RATE -gt 5 ]; then
            echo "High error rate detected!"
            exit 1
          fi
          sleep 60
        done
    
    # Promote canary
    - name: Promote Canary
      run: kubectl set image deployment/app app=myapp:$VERSION

3. Rolling Deployment

rolling-deploy:
  steps:
    - name: Configure Rolling Update
      run: |
        kubectl patch deployment app -p '{
          "spec": {
            "strategy": {
              "type": "RollingUpdate",
              "rollingUpdate": {
                "maxSurge": 1,
                "maxUnavailable": 0
              }
            }
          }
        }'
    
    - name: Update Image
      run: kubectl set image deployment/app app=myapp:$VERSION
    
    - name: Monitor Rollout
      run: kubectl rollout status deployment/app --timeout=10m

4. Feature Flags Deployment

// Deploy code but control feature activation
if (featureFlag.isEnabled('new-checkout-flow')) {
  return renderNewCheckout();
} else {
  return renderOldCheckout();
}
feature-flag-deploy:
  steps:
    - name: Deploy with Feature Off
      run: |
        FEATURE_FLAGS='{"new-checkout-flow": false}' \
        npm run deploy
    
    - name: Gradual Rollout
      run: |
        for percentage in 10 25 50 100; do
          ./set-feature-flag.sh new-checkout-flow $percentage
          sleep 3600  # Monitor for 1 hour
        done

Infrastructure as Code Integration

Terraform in CI/CD

terraform-pipeline:
  stages:
    - validate
    - plan
    - apply

  validate:
    script:
      - terraform init
      - terraform validate
      - terraform fmt -check

  plan:
    script:
      - terraform plan -out=tfplan
    artifacts:
      paths:
        - tfplan

  apply:
    script:
      - terraform apply tfplan
    when: manual
    only:
      - main

Ansible Integration

ansible-deploy:
  script:
    - ansible-playbook -i inventory/production deploy.yml
  before_script:
    - ansible-galaxy install -r requirements.yml
    - ansible-lint playbooks/

Kubernetes GitOps

# Using ArgoCD for GitOps
gitops-sync:
  script:
    - |
      # Update manifest
      yq eval '.spec.template.spec.containers[0].image = "myapp:'$VERSION'"' \
        -i k8s/deployment.yaml
      
      # Commit changes
      git add k8s/
      git commit -m "Update app to version $VERSION"
      git push origin main
      
      # ArgoCD automatically syncs

Monitoring and Observability

Pipeline Metrics

collect-metrics:
  after_script:
    - |
      # Send metrics to monitoring system
      curl -X POST https://metrics.api/pipeline \
        -d '{
          "pipeline": "$CI_PIPELINE_ID",
          "duration": "$CI_PIPELINE_DURATION",
          "status": "$CI_JOB_STATUS",
          "branch": "$CI_COMMIT_BRANCH"
        }'

Key Metrics to Track

  1. Lead Time: Commit to production
  2. Deployment Frequency: Deploys per day/week
  3. MTTR: Mean Time To Recovery
  4. Change Failure Rate: Failed deploys percentage

Observability Dashboard Example

# Grafana dashboard query
deployment_frequency:
  query: |
    count(
      ci_pipeline_status{status="success", branch="main"}
    ) by (day)

lead_time_p95:
  query: |
    histogram_quantile(0.95,
      ci_pipeline_duration_seconds_bucket
    )

GitOps Practices

The GitOps Workflow

# 1. Developer commits code
git add .
git commit -m "feat: add payment processing"
git push origin feature/payments

# 2. CI pipeline runs tests
ci-pipeline:
  - test
  - build
  - push-image

# 3. Update deployment manifest
update-manifest:
  script:
    - git clone https://github.com/myorg/k8s-configs
    - cd k8s-configs
    - yq eval '.image.tag = "'$CI_COMMIT_SHA'"' -i app/values.yaml
    - git commit -am "Update app to $CI_COMMIT_SHA"
    - git push

# 4. GitOps operator syncs
# ArgoCD/Flux automatically deploys changes

GitOps Best Practices

  1. Separate Config Repo
    app-code/          # Application source
    app-config/        # Kubernetes manifests
    app-secrets/       # Encrypted secrets (using Sealed Secrets/SOPS)
    
  2. Environment Branches
    main     → production/
    staging  → staging/
    develop  → development/
    
  3. Automated Rollback
    on-failure:
      script:
        - git revert HEAD
        - git push
        # GitOps operator automatically rolls back
    

Modern GitOps Tools:

  • ArgoCD: Most popular, great UI, multi-cluster support
  • Flux v2: GitOps toolkit, native Kubernetes controller
  • Rancher Fleet: Multi-cluster GitOps at scale
  • Weave GitOps: Enterprise features, policy management

Common Pitfalls and Troubleshooting

1. Flaky Tests

Problem: Tests pass locally but fail in CI

Solutions:

// Bad: Time-dependent test
it('expires after 1 hour', async () => {
  await sleep(3600000);  // Don't do this!
  expect(isExpired()).toBe(true);
});

// Good: Mock time
it('expires after 1 hour', async () => {
  const clock = sinon.useFakeTimers();
  clock.tick(3600000);
  expect(isExpired()).toBe(true);
  clock.restore();
});

2. Secret Leaks

Problem: Accidentally committed secrets

Prevention:

pre-commit-check:
  script:
    - gitleaks detect --source=. --verbose
    - detect-secrets scan --all-files

3. Long Build Times

Problem: Pipeline takes hours

Solutions:

# Cache dependencies
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/
    - .npm/

# Parallel jobs
test:
  parallel: 4
  script:
    - npm run test:chunk:${CI_NODE_INDEX}

# Incremental builds
build:
  script:
    - npm run build --since=$CI_COMMIT_BEFORE_SHA

4. Environment Drift

Problem: “Works in staging, breaks in production”

Solution:

# Use identical environments
.deploy_template: &deploy_template
  image: deploy:v1.2.3
  variables:
    TERRAFORM_VERSION: "1.5.0"
    KUBECTL_VERSION: "1.27.0"

deploy_staging:
  <<: *deploy_template
  environment: staging

deploy_production:
  <<: *deploy_template
  environment: production

Real-World Examples

Example 1: E-commerce Platform

Challenge: Deploy updates without affecting active shoppers

Solution:

name: E-commerce Deployment

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # 1. Build and test
      - uses: actions/checkout@v3
      - run: |
          docker build -t shop:$GITHUB_SHA .
          docker run shop:$GITHUB_SHA npm test
      
      # 2. Deploy to canary (5% traffic)
      - name: Canary Deploy
        run: |
          kubectl set image deployment/shop-canary \
            shop=shop:$GITHUB_SHA -n production
          
      # 3. Monitor metrics
      - name: Monitor Canary
        run: |
          ./scripts/monitor-canary.sh --duration=30m \
            --error-threshold=1% \
            --latency-p99=200ms
      
      # 4. Full rollout
      - name: Production Deploy
        run: |
          kubectl set image deployment/shop \
            shop=shop:$GITHUB_SHA -n production

Example 2: Microservices Platform

Challenge: Coordinate deployment of 50+ services

Solution:

# Monorepo CI/CD
name: Microservices Pipeline

on:
  push:
    branches: [main]

jobs:
  detect-changes:
    outputs:
      services: ${{ steps.filter.outputs.changes }}
    steps:
      - uses: dorny/paths-filter@v2
        id: filter
        with:
          filters: |
            auth: services/auth/**
            payment: services/payment/**
            inventory: services/inventory/**
            # ... 47 more services

  build-and-deploy:
    needs: detect-changes
    strategy:
      matrix:
        service: ${{ fromJson(needs.detect-changes.outputs.services) }}
    steps:
      - name: Build Service
        run: |
          cd services/${{ matrix.service }}
          docker build -t ${{ matrix.service }}:$GITHUB_SHA .

      - name: Deploy Service
        run: |
          helm upgrade --install ${{ matrix.service }} \
            ./charts/${{ matrix.service }} \
            --set image.tag=$GITHUB_SHA \
            --wait --timeout=5m

Example 3: Mobile App Deployment

Challenge: Deploy to multiple app stores with different requirements

Solution:

name: Mobile App Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    strategy:
      matrix:
        platform: [ios, android]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Build $
        run: |
          if [ "$" == "ios" ]; then
            fastlane ios build
          else
            fastlane android build
          fi
      
      - name: Run Tests
        run: |
          if [ "$" == "ios" ]; then
            fastlane ios test
          else
            fastlane android test
          fi

  deploy:
    needs: build
    steps:
      - name: Deploy to App Store
        run: fastlane ios release
        
      - name: Deploy to Play Store
        run: fastlane android release
        
      - name: Notify Team
        run: |
          curl -X POST $SLACK_WEBHOOK \
            -d '{"text":"Version $ released to app stores!"}'

Advanced Topics

Self-Hosted Runners

# Setup for high-security environments
self-hosted-runner:
  runs-on: [self-hosted, linux, x64, gpu]
  container:
    image: custom-runner:latest
    options: --gpus all
  steps:
    - name: ML Model Training
      run: python train.py --gpu --distributed

Pipeline as Code Libraries

// Jenkins Shared Library
@Library('company-pipeline-lib') _

companyPipeline {
  language = 'java'
  type = 'microservice'
  deployEnvironments = ['dev', 'staging', 'prod']
  slackChannel = '#deployments'
}

Multi-Cloud Deployments

multi-cloud-deploy:
  strategy:
    matrix:
      cloud: [aws, azure, gcp]
  steps:
    - name: Deploy to $
      run: |
        case "$" in
          aws)
            terraform apply -var-file=aws.tfvars
            ;;
          azure)
            terraform apply -var-file=azure.tfvars
            ;;
          gcp)
            terraform apply -var-file=gcp.tfvars
            ;;
        esac

Getting Started Checklist

Week 1: Foundation

  • Choose CI/CD platform
  • Create first “Hello World” pipeline
  • Add basic tests
  • Set up notifications

Week 2: Expansion

  • Add code quality checks (linting)
  • Implement branch protection
  • Create staging deployment
  • Add security scanning

Week 3: Optimization

  • Implement caching
  • Parallelize tests
  • Add performance tests
  • Create deployment rollback

Week 4: Production Ready

  • Set up monitoring
  • Implement blue-green deployment
  • Add compliance checks
  • Document runbooks

Resources and Further Learning

Essential Tools

  • Pipeline Syntax Validators:
    • GitHub Actions playground
    • GitLab CI Lint
    • CircleCI Config Validator
  • Security Scanners:
    • Snyk (now with AI-powered fixes)
    • SonarQube/SonarCloud
    • Checkmarx
    • GitHub Advanced Security
  • Monitoring:
    • Datadog CI Visibility
    • New Relic CodeStream
    • Grafana Cloud
    • OpenTelemetry (standard for observability)
  • GitOps Operators:
    • ArgoCD (with ApplicationSets)
    • Flux v2
    • Crossplane (infrastructure composition)

Books and Courses

  • “Continuous Delivery” by Jez Humble (Classic)
  • “The DevOps Handbook” by Gene Kim et al.
  • “Accelerate” by Nicole Forsgren et al.
  • “Modern Software Engineering” by David Farley (2022)
  • “The Phoenix Project” & “The Unicorn Project” by Gene Kim

Online Learning

  • DevOps with GitLab CI - GitLab’s official course
  • GitHub Actions Deep Dive - A Cloud Guru
  • Jenkins 2023 Masterclass - Udemy
  • CNCF CI/CD with Tekton - Linux Foundation

Community Resources

  • CNCF CI/CD Landscape
  • DevOps Weekly Newsletter
  • CI/CD Collective Forum
  1. AI-Powered CI/CD
    • Predictive test selection
    • Automated flaky test detection
    • AI-generated pipeline optimizations
    • Smart deployment timing
  2. Supply Chain Security
    • SBOM (Software Bill of Materials) generation
    • SLSA compliance automation
    • Sigstore for artifact signing
    • Dependency attestation
  3. Platform Engineering
    • Internal Developer Platforms (IDPs)
    • Golden paths for deployment
    • Self-service infrastructure
    • Developer experience metrics
  4. Green CI/CD
    • Carbon-aware computing
    • Energy-efficient build scheduling
    • Resource optimization
    • Sustainability metrics

Remember: CI/CD is a journey, not a destination. Start simple, measure everything, and continuously improve your pipeline based on what you learn. The goal isn’t perfection—it’s progress.


See Also

  • Git Version Control - Distributed version control system fundamentals
  • Docker - Containerization for consistent build environments
  • Kubernetes - Container orchestration and automated deployments
  • Terraform - Infrastructure as code for automated provisioning
  • AWS - Cloud deployment platforms and services
  • Cybersecurity - Security best practices for CI/CD pipelines