Skip to the content.
Lesson 4 55 min GitHub Actions CI/CD Pipelines

CI/CD Engineering

Design and build reliable delivery pipelines with GitHub Actions — from first commit to production deploy, with automated testing, security gates, and artifact management.

This lesson is private to enrolled students. Please keep the link to yourself — thanks.

What You Will Learn

  • Design a complete CI/CD pipeline from scratch using GitHub Actions
  • Implement testing, security scanning, and quality gates
  • Build and push Docker images to a container registry
  • Manage secrets and environment promotion safely
  • Understand deployment strategies: rolling, blue/green, canary

1. Pipeline Design Principles

A good CI/CD pipeline is fast, reliable, and safe.

Developer pushes code
        │
        ▼
  ┌─────────────┐
  │   CI Stage  │  < 5 minutes
  │  lint + test│
  │  build image│
  │  scan image │
  └──────┬──────┘
         │ passes
         ▼
  ┌─────────────┐
  │  CD: Staging│  automatic
  │  deploy     │
  │  smoke test │
  └──────┬──────┘
         │ passes
         ▼
  ┌─────────────┐
  │ CD: Prod    │  manual approval
  │  deploy     │  or auto on tag
  └─────────────┘

The “shift left” principle

Move quality checks as early as possible. A linter failure at second 15 is better than a test failure at minute 8.


2. GitHub Actions Pipeline

Complete workflow example

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

on:
  push:
    branches: [main, 'release/**']
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: $

jobs:
  # ── Stage 1: Quality ──────────────────────────────────────────
  quality:
    name: Lint & Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: Lint
        run: npm run lint

      - name: Unit tests
        run: npm test -- --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v4

  # ── Stage 2: Build & Scan ─────────────────────────────────────
  build:
    name: Build & Push Image
    needs: quality
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    outputs:
      image-digest: $

    steps:
      - uses: actions/checkout@v4

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: $
          username: $
          password: $

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: $/$
          tags: |
            type=sha,prefix=,suffix=,format=short
            type=ref,event=branch
            type=semver,pattern=

      - name: Build and push
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: $
          tags: $
          labels: $
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Scan image for vulnerabilities
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: $/$:$
          exit-code: '1'
          severity: 'HIGH,CRITICAL'

  # ── Stage 3: Deploy to Staging ────────────────────────────────
  deploy-staging:
    name: Deploy → Staging
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4
      - name: Deploy to staging
        run: |
          echo "Deploying $ to staging"
          # kubectl set image deployment/app app=$IMAGE:$SHA

3. Secrets Management

Never hardcode secrets. GitHub Actions has three layers:

Level Scope Use for
Repository Single repo Repo-specific API keys
Environment Specific environments (prod/stg) Production credentials
Organisation All repos in org Shared infra credentials
steps:
  - name: Deploy
    env:
      DATABASE_URL: $
      API_KEY: $
    run: ./deploy.sh
⚠️
Never echo secrets GitHub masks known secrets in logs, but avoid echo $SECRET — encoding tricks can bypass masking. Pass secrets as env vars to commands, never as CLI arguments.

4. Deployment Strategies

Rolling deployment (default in Kubernetes)

v1 v1 v1 v1    →    v2 v1 v1 v1    →    v2 v2 v1 v1    →    v2 v2 v2 v2
  • Zero-downtime if configured correctly (maxSurge, maxUnavailable)
  • Simple to implement and roll back

Blue/Green deployment

Traffic: 100% → Blue (v1)

Deploy Green (v2), run smoke tests
Traffic: 100% → Green (v2)   (instant cutover)

Keep Blue running for fast rollback

Canary deployment

Traffic: 95% → Stable (v1)
         5% → Canary (v2)    ← monitor error rate and latency

Gradually increase: 5% → 20% → 50% → 100%
Abort if metrics degrade

5. Hands-on Exercise

  1. Create a .github/workflows/ci.yml for an existing project with lint + test jobs
  2. Add a Docker build step that pushes to GHCR on merge to main
  3. Add Trivy image scanning with a HIGH/CRITICAL failure gate
  4. Add a staging deployment job that runs automatically on main
  5. Add a production deployment job that requires manual approval

Summary

Concept Key takeaway
Pipeline stages Quality → Build → Staging → Production — each stage gates the next
Shift left Run cheap checks first; fail fast on quality issues
Secrets management Use GitHub Environments for scoped production credentials
Rolling Default K8s strategy — gradual replacement with rollback support
Canary Route a small percentage of traffic first — safest for production

Discussion & Questions

Ask questions, share what you built, or leave feedback about this lesson. GitHub account required.