Manually deploying code to production by SSH-ing into a server and running git pull is a recipe for disaster. CI/CD (Continuous Integration / Continuous Deployment) automates the entire process: every code push triggers tests, builds, and deployments — consistently, reliably, and without human error.

CI vs CD

  • Continuous Integration (CI): Automatically build and test code every time a developer pushes changes. Catches bugs early.
  • Continuous Delivery (CD): Automatically prepare code for release. Every commit that passes tests is deployment-ready.
  • Continuous Deployment: Automatically deploy every commit that passes tests to production. No manual approval step.

A Typical CI/CD Pipeline

Pipeline Stages
1. 📥 Source      → Developer pushes code to Git
2. 🔨 Build       → Install dependencies, compile TypeScript, bundle assets
3. 🧪 Test        → Run unit tests, integration tests, linting
4. 🔍 Analysis    → Code quality checks, security scanning
5. 📦 Package     → Build Docker image or create deployment artifact
6. 🚀 Deploy      → Push to staging, then production
7. ✅ Verify      → Health checks, smoke tests, monitoring alerts

GitHub Actions Example

.github/workflows/ci.yml
name: CI/CD Pipeline

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

jobs:
  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'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to production
        run: |
          echo "Deploying to production..."
          # Your deployment script here
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

Deployment Strategies

  • Rolling Deployment: Gradually replace old instances with new ones. Zero downtime, but both versions run simultaneously during the rollout.
  • Blue-Green Deployment: Maintain two identical environments. Deploy to the inactive one, then switch traffic. Instant rollback by switching back.
  • Canary Deployment: Route a small percentage (e.g., 5%) of traffic to the new version. If metrics look good, gradually increase to 100%.
  • Feature Flags: Deploy new code behind a flag. Enable for specific users or percentages without redeploying.

Testing in CI

Test Pyramid
           /  E2E Tests  \          ← Slow, expensive, few
          / Integration Tests \      ← Medium speed, moderate count
         /    Unit Tests         \   ← Fast, cheap, many

Run order in CI:
1. Lint (fastest) → catch style issues
2. Unit tests → catch logic bugs
3. Integration tests → catch wiring bugs
4. E2E tests → catch user-flow bugs

Best Practices

  • Keep pipelines fast — aim for under 10 minutes. Parallelize tests, cache dependencies.
  • Fail fast — run the fastest checks (lint, type-check) first.
  • Never skip tests to deploy faster. If tests are too slow, optimize them.
  • Use secrets management — never hardcode credentials in pipeline configs.
  • Make deployments boring — if deploying is scary, you're not doing it often enough.
  • Always have a rollback plan — one-click rollback to the previous version.

Try Our Free Developer Tools

Minify and optimize your code before deployment.