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
1// CI/CD Configuration Examples23// === .github/workflows/ci.yml (GitHub Actions) ===4const githubActionsCI = `5name: CI/CD Pipeline67on:8 push:9 branches: [main, develop]10 pull_request:11 branches: [main]1213env:14 NODE_VERSION: '20'15 REGISTRY: ghcr.io16 IMAGE_NAME: ${{ github.repository }}1718jobs:19 # Stage 1: Lint & Type Check20 lint:21 runs-on: ubuntu-latest22 steps:23 - uses: actions/checkout@v424 - uses: actions/setup-node@v425 with:26 node-version: ${{ env.NODE_VERSION }}27 cache: 'npm'28 - run: npm ci29 - run: npm run lint30 - run: npm run type-check # If using TypeScript3132 # Stage 2: Test33 test:34 runs-on: ubuntu-latest35 needs: lint36 services:37 postgres:38 image: postgres:16-alpine39 env:40 POSTGRES_DB: test_db41 POSTGRES_PASSWORD: test_password42 ports:43 - 5432:543244 options: >-45 --health-cmd pg_isready46 --health-interval 10s47 --health-timeout 5s48 --health-retries 549 redis:50 image: redis:7-alpine51 ports:52 - 6379:637953 steps:54 - uses: actions/checkout@v455 - uses: actions/setup-node@v456 with:57 node-version: ${{ env.NODE_VERSION }}58 cache: 'npm'59 - run: npm ci60 - run: npm test -- --coverage61 env:62 DATABASE_URL: postgresql://postgres:test_password@localhost:5432/test_db63 REDIS_URL: redis://localhost:637964 - uses: codecov/codecov-action@v3 # Upload coverage6566 # Stage 3: Build & Push Docker Image67 build:68 runs-on: ubuntu-latest69 needs: test70 if: github.ref == 'refs/heads/main'71 permissions:72 contents: read73 packages: write74 steps:75 - uses: actions/checkout@v476 - uses: docker/login-action@v377 with:78 registry: ${{ env.REGISTRY }}79 username: ${{ github.actor }}80 password: ${{ secrets.GITHUB_TOKEN }}81 - uses: docker/build-push-action@v582 with:83 push: true84 tags: |85 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest86 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}8788 # Stage 4: Deploy89 deploy:90 runs-on: ubuntu-latest91 needs: build92 if: github.ref == 'refs/heads/main'93 steps:94 - name: Deploy to production95 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-deployment100 # Kubernetes: kubectl set image deployment/api api=image:tag101`;102103// === 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}120121module.exports = { githubActionsCI };
🏋️ Practice Exercise
Exercises:
- Set up a GitHub Actions CI pipeline: lint → test → build for a Node.js project
- Add a test stage with PostgreSQL and Redis as service containers
- Implement Docker image building and pushing to a container registry in CI
- Set up automatic deployment on merge to
mainbranch - Implement blue-green deployment with health checks before traffic switching
- 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 installwithoutpackage-lock.jsoncan install different versions than developmentStoring 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.