CI/CD & Deployment Strategies

📖 Concept

CI/CD (Continuous Integration / Continuous Deployment) automates the process of testing, building, and deploying your Node.js application every time code is pushed.

CI/CD pipeline stages:

Code Push → Lint → Test → Build → Deploy → Monitor
    │         │       │       │        │         │
    Git     ESLint   Jest   Docker   Production  Datadog

Popular CI/CD platforms:

Platform Pros
GitHub Actions Free for public repos, tight GitHub integration
GitLab CI Built into GitLab, powerful pipelines
Jenkins Self-hosted, highly customizable
CircleCI Fast, Docker-first
AWS CodePipeline Native AWS integration

Deployment strategies:

Strategy Description Risk
Rolling Replace instances one by one Low (gradual)
Blue-Green Switch traffic between two identical environments Very low (instant rollback)
Canary Route small % of traffic to new version Lowest (test in production)
Recreate Stop old, start new High (downtime)

Environment management:

Development → Staging → Production
    │            │           │
  .env.dev   .env.staging  Cloud secrets
  Local DB    Test DB       Production DB

🏠 Real-world analogy: CI/CD is like a car assembly line with quality checks. Each station (stage) inspects the car (code) for defects (bugs). Only cars that pass every station (lint, test, build) reach the showroom (production). Blue-green deployment is like having two showroom floors — customers are seamlessly redirected to the floor with newer models.

💻 Code Example

codeTap to expand ⛶
1// CI/CD Configuration Examples
2
3// === .github/workflows/ci.yml (GitHub Actions) ===
4const githubActionsCI = `
5name: CI/CD Pipeline
6
7on:
8 push:
9 branches: [main, develop]
10 pull_request:
11 branches: [main]
12
13env:
14 NODE_VERSION: '20'
15 REGISTRY: ghcr.io
16 IMAGE_NAME: ${{ github.repository }}
17
18jobs:
19 # Stage 1: Lint & Type Check
20 lint:
21 runs-on: ubuntu-latest
22 steps:
23 - uses: actions/checkout@v4
24 - uses: actions/setup-node@v4
25 with:
26 node-version: ${{ env.NODE_VERSION }}
27 cache: 'npm'
28 - run: npm ci
29 - run: npm run lint
30 - run: npm run type-check # If using TypeScript
31
32 # Stage 2: Test
33 test:
34 runs-on: ubuntu-latest
35 needs: lint
36 services:
37 postgres:
38 image: postgres:16-alpine
39 env:
40 POSTGRES_DB: test_db
41 POSTGRES_PASSWORD: test_password
42 ports:
43 - 5432:5432
44 options: >-
45 --health-cmd pg_isready
46 --health-interval 10s
47 --health-timeout 5s
48 --health-retries 5
49 redis:
50 image: redis:7-alpine
51 ports:
52 - 6379:6379
53 steps:
54 - uses: actions/checkout@v4
55 - uses: actions/setup-node@v4
56 with:
57 node-version: ${{ env.NODE_VERSION }}
58 cache: 'npm'
59 - run: npm ci
60 - run: npm test -- --coverage
61 env:
62 DATABASE_URL: postgresql://postgres:test_password@localhost:5432/test_db
63 REDIS_URL: redis://localhost:6379
64 - uses: codecov/codecov-action@v3 # Upload coverage
65
66 # Stage 3: Build & Push Docker Image
67 build:
68 runs-on: ubuntu-latest
69 needs: test
70 if: github.ref == 'refs/heads/main'
71 permissions:
72 contents: read
73 packages: write
74 steps:
75 - uses: actions/checkout@v4
76 - uses: docker/login-action@v3
77 with:
78 registry: ${{ env.REGISTRY }}
79 username: ${{ github.actor }}
80 password: ${{ secrets.GITHUB_TOKEN }}
81 - uses: docker/build-push-action@v5
82 with:
83 push: true
84 tags: |
85 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
86 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
87
88 # Stage 4: Deploy
89 deploy:
90 runs-on: ubuntu-latest
91 needs: build
92 if: github.ref == 'refs/heads/main'
93 steps:
94 - name: Deploy to production
95 run: |
96 echo "Deploying to production..."
97 # Examples:
98 # SSH: ssh deploy@server "docker pull image && docker-compose up -d"
99 # AWS ECS: aws ecs update-service --cluster prod --service api --force-new-deployment
100 # Kubernetes: kubectl set image deployment/api api=image:tag
101`;
102
103// === Deployment health check script ===
104async function healthCheck(url, maxRetries = 10, delayMs = 3000) {
105 for (let i = 0; i < maxRetries; i++) {
106 try {
107 const response = await fetch(`${url}/health`);
108 if (response.ok) {
109 const data = await response.json();
110 console.log("Health check passed:", data);
111 return true;
112 }
113 } catch (err) {
114 console.log(`Attempt ${i + 1}/${maxRetries} failed. Retrying in ${delayMs}ms...`);
115 }
116 await new Promise((r) => setTimeout(r, delayMs));
117 }
118 throw new Error("Health check failed after maximum retries");
119}
120
121module.exports = { githubActionsCI };

🏋️ Practice Exercise

Exercises:

  1. Set up a GitHub Actions CI pipeline: lint → test → build for a Node.js project
  2. Add a test stage with PostgreSQL and Redis as service containers
  3. Implement Docker image building and pushing to a container registry in CI
  4. Set up automatic deployment on merge to main branch
  5. Implement blue-green deployment with health checks before traffic switching
  6. Add code coverage reporting and enforce minimum coverage thresholds in CI

⚠️ Common Mistakes

  • Not running tests in CI that match the production environment — use the same database type (PostgreSQL, not SQLite) and Node.js version

  • Deploying without health checks — always verify the new deployment is healthy before routing traffic

  • Not using lockfiles in CI — npm install without package-lock.json can install different versions than development

  • Storing secrets in code or CI config files — use encrypted secrets (GitHub Secrets, AWS Secrets Manager)

  • Not having a rollback plan — every deployment should be reversible within minutes; use blue-green or keep the previous Docker image

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for CI/CD & Deployment Strategies. Login to unlock this feature.