Continuous Integration & Continuous Deployment
Automatically build and test code changes frequently (multiple times per day).
Code is always in deployable state. Manual approval for production.
Fully automated - every change goes to production automatically.
| Aspect | CI | Continuous Delivery | Continuous Deployment |
|---|---|---|---|
| Build & Test | Automated | Automated | Automated |
| Deploy to Staging | Manual | Automated | Automated |
| Deploy to Production | Manual | Manual (approved) | Automated |
| Deployment Frequency | Varies | Daily/Weekly | Multiple per day |
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Commit │───>│ Build │───>│ Test │───>│ Package │───>│ Deploy │
│ (Git Push) │ │ (Compile) │ │(Unit, Integ)│ │ (Docker) │ │ (Staging) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│
v
┌─────────────────┐
│Manual Approval │
│ (for prod) │
└─────────────────┘
│
v
┌─────────────────┐
│Deploy Production│
└─────────────────┘
name: Build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Build
run: npm run build
- name: Run unit tests
run: npm test -- --coverage
- name: Run integration tests
run: npm run test:integration
- name: Security scan
run: npm audit
- name: Upload coverage
uses: codecov/codecov-action@v3
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} myapp:latest
- name: Push to registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push myapp:${{ github.sha }}
docker push myapp:latest
# GitHub Actions - Cache node_modules
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# GitLab CI - Cache
cache:
paths:
- node_modules/
- .npm/
Result: 5-minute build → 30-second build
# Run tests in parallel
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- run: npm test
integration-tests:
runs-on: ubuntu-latest
steps:
- run: npm run test:integration
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
# All run simultaneously
# Use BuildKit for better caching
DOCKER_BUILDKIT=1 docker build .
# Use registry as cache
docker build --cache-from myapp:latest -t myapp:new .
Two identical environments. Switch traffic from old (blue) to new (green).
Before Deployment:
┌────────────┐
│ Users │
└─────┬──────┘
│
v
┌─────────────┐ ┌─────────────┐
│ Load │ │ │
│ Balancer │ │ │
└──────┬──────┘ │ │
│ │ │
v │ │
┌─────────────┐ │ │
│ Blue (v1.0) │ │ Green (idle)│
│ ████████ │ │ │
└─────────────┘ └─────────────┘
After Deployment (instant switch):
┌────────────┐
│ Users │
└─────┬──────┘
│
v
┌─────────────┐ ┌─────────────┐
│ Load │ │ │
│ Balancer │ │ │
└──────┬──────┘ │ │
│ │ │
v v │
┌─────────────┐ ┌─────────────┐
│ Blue (v1.0) │ │ Green (v2.0)│
│ (idle) │ │ ████████ │
└─────────────┘ └─────────────┘
Roll out to small subset of users first. Gradually increase traffic if healthy.
Phase 1: 5% traffic to canary
┌────────────┐
│ Users │
└─────┬──────┘
│
v
┌─────────────┐
│ Load │ 95% ──> v1.0 (stable)
│ Balancer │ 5% ──> v2.0 (canary)
└─────────────┘
Phase 2: 50% traffic (if healthy)
┌─────────────┐
│ Load │ 50% ──> v1.0
│ Balancer │ 50% ──> v2.0
└─────────────┘
Phase 3: 100% traffic
┌─────────────┐
│ Load │ 100% ─> v2.0
│ Balancer │
└─────────────┘
# Kubernetes Canary with Flagger
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: myapp
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
service:
port: 8080
analysis:
interval: 1m
threshold: 5
maxWeight: 50
stepWeight: 10
canaryAnalysis:
metrics:
- name: request-success-rate
thresholdRange:
min: 99
- name: request-duration
thresholdRange:
max: 500
Gradually replace instances one-by-one or in batches.
Step 1: Replace 1 instance v1: [██] [██] [██] [ ] v2: [ ] [ ] [ ] [██] Step 2: Replace another v1: [██] [██] [ ] [ ] v2: [ ] [ ] [██] [██] Step 3: Replace all v1: [ ] [ ] [ ] [ ] v2: [██] [██] [██] [██]
# Kubernetes RollingUpdate
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # Create 2 extra pods during update
maxUnavailable: 1 # Max 1 pod unavailable at a time
Deploy code to production but keep features disabled. Enable for specific users.
// Feature flag example
if (featureFlags.isEnabled('newCheckout', user.id)) {
return ;
} else {
return ;
}
// Gradually increase percentage
featureFlags.setRolloutPercentage('newCheckout', 10); // 10% of users
Use Git as single source of truth for infrastructure and application config. Changes to Git automatically deployed.
Traditional Push-based CD:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Git │──────>│ CI │──────>│ Cluster │
│ Commit │ │ Pipeline │ │ (push) │
└──────────┘ └──────────┘ └──────────┘
GitOps Pull-based:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Git │<──────│ Agent │<──────│ Cluster │
│ Commit │ │ (ArgoCD) │ │ (pull) │
└──────────┘ └──────────┘ └──────────┘
Polls Git every 3min,
applies if changed
| Tool | Description | Use Case |
|---|---|---|
| Argo CD | Declarative GitOps for Kubernetes | Most popular, rich UI, multi-cluster |
| Flux | GitOps operator for Kubernetes | CNCF project, lightweight |
| Jenkins X | CI/CD + GitOps for Kubernetes | Full CI/CD platform |
| Terraform Cloud | GitOps for infrastructure | Infrastructure as Code |
# Application definition
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
spec:
source:
repoURL: https://github.com/org/repo
targetRevision: main
path: k8s/manifests
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Delete resources not in Git
selfHeal: true # Auto-sync if drift detected
| Tool | Hosting | Pros | Cons |
|---|---|---|---|
| GitHub Actions | Cloud (SaaS) | Integrated with GitHub, easy setup, marketplace | Vendor lock-in, limited free minutes |
| GitLab CI/CD | Cloud or Self-hosted | Integrated with GitLab, powerful, free tier generous | Steeper learning curve |
| Jenkins | Self-hosted | Very flexible, huge plugin ecosystem, free | Complex setup, maintenance overhead, UI dated |
| CircleCI | Cloud (SaaS) | Fast, good caching, Docker support | Expensive at scale, config can be complex |
| Travis CI | Cloud (SaaS) | Simple, good for open source | Declining popularity, limited features |
| Azure Pipelines | Cloud (SaaS) | Integrated with Azure, generous free tier | Complex YAML, Microsoft ecosystem |
Store pipeline config in Git with application code.
# .github/workflows/ci.yml (version controlled)
# .gitlab-ci.yml
# Jenkinsfile
Benefits: Versioned, reviewed, auditable
Run fastest/cheapest checks first.
# Good order:
1. Lint (seconds, cheap)
2. Unit tests (seconds to minutes)
3. Build (minutes)
4. Integration tests (minutes)
5. E2E tests (10-30 minutes, expensive)
# Don't run expensive tests if lint fails!
# Build once
docker build -t myapp:abc123 .
# Deploy to environments with same image
# Dev:
docker run -e ENV=dev myapp:abc123
# Prod:
docker run -e ENV=prod myapp:abc123
Dev, staging, prod should be as similar as possible.
# GitHub Actions - Use secrets
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: ./deploy.sh
# Kubernetes - Health checks
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5