All topics
Frontend · Learning hub

Cicd notes for developers

Master Cicd with a curated set of 3 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Frontend notes
Cicd

CI/CD Concepts & Pipelines

CI/CD Concepts & Pipelines Core Concepts Continuous Integration (CI) — developers merge to main frequently (daily+); each push triggers automated build + test.

CI/CD Concepts & Pipelines

Core Concepts

  • Continuous Integration (CI) — developers merge to main frequently (daily+); each push triggers automated build + test. Finds bugs early, prevents merge hell

  • Continuous Delivery (CD) — every passing build is deployable to production; deployment triggered manually. Artifact is always release-ready

  • Continuous Deployment — every passing build automatically deploys to production. Requires high test confidence and good monitoring/rollback

  • Pipeline stages — typically: Source → Build → Test → Security Scan → Staging Deploy → Smoke Tests → Production Deploy

  • Artifact — build output stored and versioned (Docker image, JAR, zip). Same artifact deployed across all envs (no rebuilds per env)

Deployment Strategies

Blue/Green Deployment:
  Two identical environments (blue = live, green = new)
  Deploy to green → run smoke tests → switch traffic to green
  Blue stays up for instant rollback
  Cost: double infrastructure

Canary Deployment:
  Route small % (1-5%) of traffic to new version
  Monitor error rates, latency → gradually increase %
  Abort if metrics degrade (automatic or manual)
  Tools: Argo Rollouts, AWS CodeDeploy, Nginx/Istio

Rolling Update:
  Replace instances one by one
  Always some of old and new running simultaneously
  Default K8s strategy (maxUnavailable, maxSurge params)
  Cheaper than blue/green; risk of version mismatch

Feature Flags (Feature Toggles):
  Deploy code but enable features selectively
  Decouple deployment from release
  Tools: LaunchDarkly, Unleash, Flagsmith, Vercel Edge Config
  Allows: A/B tests, gradual rollouts, kill switches

Shadow Deployment:
  Mirror production traffic to new service
  Compare responses — new version doesn't serve real users yet
  Good for testing with real data without risk

Pipeline as Code

# GitLab CI — .gitlab-ci.yml
stages: [test, build, deploy]

variables:
  IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

test:
  stage: test
  image: node:20
  cache:
    key: $CI_COMMIT_REF_SLUG
    paths: [node_modules/]
  script:
    - npm ci
    - npm run lint
    - npm run test -- --coverage
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'

build:
  stage: build
  image: docker:24
  services: [docker:24-dind]
  script:
    - docker build -t $IMAGE .
    - docker push $IMAGE
  only: [main]

deploy-staging:
  stage: deploy
  image: bitnami/kubectl
  environment:
    name: staging
    url: https://staging.myapp.com
  script:
    - kubectl set image deployment/app app=$IMAGE -n staging
    - kubectl rollout status deployment/app -n staging
  only: [main]

deploy-production:
  stage: deploy
  environment:
    name: production
    url: https://myapp.com
  script:
    - kubectl set image deployment/app app=$IMAGE -n production
    - kubectl rollout status deployment/app -n production
  when: manual                # require approval
  only: [main]

CI/CD Best Practices

  • Fast feedback — keep CI under 10 minutes. Parallelize test suites, cache dependencies, run unit tests first (fail fast)

  • Immutable artifacts — build once, deploy the same image across staging and production. Never rebuild per environment

  • Secrets management — never hardcode secrets. Use CI secret variables, Vault, AWS Secrets Manager, or OIDC (keyless auth)

  • Rollback strategy — always define how to roll back. K8s: kubectl rollout undo. Keep previous image available. Feature flags for quick disable

  • Branch strategy — trunk-based dev (short-lived branches, merge daily) preferred over GitFlow for CD. Feature flags replace long-lived branches

Cicd

CI/CD Fundamentals

CI/CD Fundamentals CI/CD automates the path from code commit to production. CI catches bugs early by running tests on every push. CD ensures the artifact is alw

CI/CD Fundamentals

CI/CD automates the path from code commit to production. CI catches bugs early by running tests on every push. CD ensures the artifact is always deployable — or deployed automatically.

GitHub Actions — Complete Workflow

# .github/workflows/ci.yml
name: CI

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

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

jobs:
  lint-and-test:
    name: Lint & Test
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'              # cache node_modules

      - name: Install dependencies
        run: npm ci                 # clean install from lockfile

      - name: Lint
        run: npm run lint

      - name: Type check
        run: npm run typecheck

      - name: Run tests
        run: npm run test -- --coverage
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage/lcov.info

Build & Docker in CI

  build-and-push:
    name: Build & Push Docker Image
    runs-on: ubuntu-latest
    needs: lint-and-test            # only run if tests pass
    if: github.ref == 'refs/heads/main'

    permissions:
      contents: read
      packages: write               # push to GitHub Container Registry

    steps:
      - uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

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

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha      # GitHub Actions cache
          cache-to: type=gha,mode=max
          build-args: |
            APP_VERSION=${{ github.sha }}

Secrets, Environments & Artifacts

  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: build-and-push
    environment:
      name: staging
      url: https://staging.myapp.com

    steps:
      - name: Deploy to staging
        run: |
          curl -X POST ${{ secrets.DEPLOY_WEBHOOK_STAGING }} \
            -H 'Authorization: Bearer ${{ secrets.DEPLOY_TOKEN }}' \
            -d '{"image": "${{ env.IMAGE_NAME }}:${{ github.sha }}"}'

  # Upload/download build artifacts
  build:
    steps:
      - name: Build
        run: npm run build

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 7

  deploy:
    needs: build
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Deploy to Vercel
        run: npx vercel --prod --token ${{ secrets.VERCEL_TOKEN }}

Matrix Builds & Reusable Workflows

# Matrix strategy — test across multiple versions
jobs:
  test:
    strategy:
      matrix:
        node: [18, 20, 22]
        os: [ubuntu-latest, windows-latest]
      fail-fast: false              # don't cancel others if one fails
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci && npm test

# Reusable workflow — .github/workflows/deploy.yml
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      DEPLOY_TOKEN:
        required: true

jobs:
  deploy:
    environment: ${{ inputs.environment }}
    runs-on: ubuntu-latest
    steps:
      - run: echo 'Deploying to ${{ inputs.environment }}'

# Caller workflow
jobs:
  deploy-prod:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
    secrets:
      DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Cicd

Deployment Patterns

Deployment Patterns Choosing the right deployment strategy reduces downtime, limits blast radius, and enables fast rollback. The strategy depends on your infras

Deployment Patterns

Choosing the right deployment strategy reduces downtime, limits blast radius, and enables fast rollback. The strategy depends on your infrastructure, team confidence, and risk tolerance.

Blue-Green Deployment

# Blue-Green with Kubernetes and a load balancer
# Two identical deployments; switch traffic by updating the Service selector

# Deploy new version to green
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      slot: green
  template:
    metadata:
      labels:
        app: myapp
        slot: green
    spec:
      containers:
        - name: myapp
          image: myapp:v2.0.0

---
# Service currently pointing to blue
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
    slot: blue       # switch to 'green' after smoke tests
  ports:
    - port: 80
      targetPort: 3000

# Switching traffic (after green smoke tests pass):
# kubectl patch service myapp -p '{"spec":{"selector":{"slot":"green"}}}'
# Rollback (instant):
# kubectl patch service myapp -p '{"spec":{"selector":{"slot":"blue"}}}'

Canary Releases

# Canary with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp
spec:
  replicas: 10
  strategy:
    canary:
      steps:
        - setWeight: 10            # 10% traffic to canary
        - pause: { duration: 5m }  # wait 5 min, monitor metrics
        - setWeight: 30
        - pause: { duration: 10m }
        - setWeight: 60
        - pause: { duration: 10m }
        - setWeight: 100           # full rollout
      analysis:
        templates:
          - templateName: error-rate
        args:
          - name: service-name
            value: myapp

---
# AnalysisTemplate — auto-rollback if error rate > 5%
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: error-rate
spec:
  metrics:
    - name: error-rate
      interval: 1m
      successCondition: result[0] < 0.05
      failureLimit: 3
      provider:
        prometheus:
          address: http://prometheus:9090
          query: |
            sum(rate(http_requests_total{status=~"5.."}[1m]))
            / sum(rate(http_requests_total[1m]))

Feature Flags

// Feature flags — decouple deployment from release
// Tools: LaunchDarkly, Unleash, Flagsmith, GrowthBook, Vercel Edge Config

// LaunchDarkly example
import { init } from 'launchdarkly-node-server-sdk';

const client = init(process.env.LAUNCHDARKLY_SDK_KEY!);
await client.waitForInitialization();

// Evaluate a flag for a user
const context = { kind: 'user', key: user.id, email: user.email, plan: 'pro' };
const showNewDashboard = await client.variation('new-dashboard', context, false);

if (showNewDashboard) {
  // Show new feature to % of users
}

// React hook with OpenFeature (vendor-agnostic)
import { useBooleanFlagValue } from '@openfeature/react-sdk';

function Dashboard() {
  const showNewChart = useBooleanFlagValue('new-chart', false);
  return showNewChart ? <NewChart /> : <OldChart />;
}

// Kill switch pattern — emergency disable without deploy
// 1. Check flag before expensive operation
// 2. Set flag to false in dashboard to instantly disable
// 3. No code change or deployment needed
async function processPayment(order: Order) {
  const paymentsEnabled = await flags.get('payments-enabled', true);
  if (!paymentsEnabled) throw new Error('Payments temporarily disabled');
  return stripe.createCharge(order);
}

Rollback Strategies & Monitoring

# Kubernetes rollback
kubectl rollout status deployment/myapp          # check rollout health
kubectl rollout history deployment/myapp         # view revision history
kubectl rollout undo deployment/myapp            # rollback to previous revision
kubectl rollout undo deployment/myapp --to-revision=3   # rollback to specific revision

# Docker Swarm rollback
docker service update --rollback myapp

# Vercel — instant rollback to any previous deployment
vercel rollback [deployment-url]

# Database migrations — always write reversible migrations
# Forward: ALTER TABLE users ADD COLUMN feature_flags jsonb DEFAULT '{}';
# Backward: ALTER TABLE users DROP COLUMN feature_flags;

# Zero-downtime DB schema changes (expand-contract pattern):
# Step 1 — Expand: add new column (nullable, no constraint yet)
# Step 2 — Migrate: backfill data in batches
# Step 3 — Contract: add NOT NULL constraint, drop old column

# Health check endpoint — used by load balancers to detect failures
# GET /health → 200 { status: "ok", db: "ok", cache: "ok" }
# GET /ready  → 200 when ready to serve traffic (startup probe)

Keep your Cicd knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever