CI/CD pipeline design, automated testing, deployment strategies, and continuous delivery best practices
Scope: Comprehensive guide to CI/CD pipelines, automated testing, deployment strategies, and delivery practices Lines: ~350 Last Updated: 2025-10-27 Format Version: 1.0 (Atomic)
Activate this skill when:
Definition: Automatically build and test code on every commit
Key Principles:
Benefits:
Definition: Every change can be deployed to production automatically
Continuous Integration
↓
Continuous Delivery (can deploy any time)
↓
Continuous Deployment (auto-deploy to production)
Deployment Strategies:
Typical CI/CD Pipeline:
Commit → Build → Test → Deploy → Monitor
↓ ↓ ↓ ↓ ↓
Code Compile Unit Staging Metrics
Package Lint Prod Alerts
E2E
Basic CI Pipeline:
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Lint with flake8
run: |
flake8 src/ --max-complexity=10
- name: Type check with mypy
run: |
mypy src/
- name: Run tests
run: |
pytest tests/ --cov=src --cov-fail-under=80
- name: Upload coverage
uses: codecov/codecov-action@v3
Comprehensive Pipeline:
# .github/workflows/pipeline.yml
name: Full Pipeline
on:
push:
branches: [main]
jobs:
# Stage 1: Build
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Push to registry
run: docker push myapp:${{ github.sha }}
# Stage 2: Test
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Unit tests
run: npm test
- name: Integration tests
run: npm run test:integration
- name: E2E tests
run: npm run test:e2e
# Stage 3: Security scan
security:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Snyk scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# Stage 4: Deploy to staging
deploy-staging:
needs: [test, security]
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: |
kubectl set image deployment/myapp \
myapp=myapp:${{ github.sha }} \
-n staging
# Stage 5: Deploy to production (manual approval)
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: |
kubectl set image deployment/myapp \
myapp=myapp:${{ github.sha }} \
-n production
Fail Build on Quality Issues:
- name: Check code coverage
run: |
pytest --cov=src --cov-fail-under=80
# Fails if coverage < 80%
- name: Check code complexity
run: |
radon cc src/ -a -nb --total-average-threshold=B
# Fails if complexity > B grade
- name: Check for vulnerabilities
run: |
safety check
# Fails if known vulnerabilities found
- name: Check for code duplication
run: |
jscpd src/ --threshold 5
# Fails if > 5% duplication
- name: Check for linting errors
run: |
eslint src/ --max-warnings=0
# Fails on any warnings
# Blue-Green deployment strategy
- name: Deploy to green environment
run: |
# Deploy new version to "green" environment
kubectl apply -f k8s/green/ -n production
- name: Run smoke tests on green
run: |
curl https://green.myapp.com/health
npm run test:smoke -- --url=https://green.myapp.com
- name: Switch traffic to green
run: |
# Switch load balancer to green
kubectl patch service myapp \
-p '{"spec":{"selector":{"version":"green"}}}' \
-n production
- name: Monitor green for 10 minutes
run: |
# Monitor error rates, latency
sleep 600
- name: Destroy blue environment
run: |
kubectl delete -f k8s/blue/ -n production
# Gradual rollout to production
- name: Deploy canary (5% traffic)
run: |
kubectl set image deployment/myapp-canary \
myapp=myapp:${{ github.sha }}
kubectl scale deployment/myapp-canary --replicas=1
- name: Monitor canary for 15 minutes
run: |
# Check error rates, latency
if [ $ERROR_RATE -gt $THRESHOLD ]; then
echo "Canary failed, rolling back"
exit 1
fi
- name: Increase to 25% traffic
run: |
kubectl scale deployment/myapp-canary --replicas=5
- name: Monitor for 15 minutes
run: |
# Check metrics again
sleep 900
- name: Full rollout (100% traffic)
run: |
kubectl set image deployment/myapp \
myapp=myapp:${{ github.sha }}
kubectl delete deployment/myapp-canary
Deploy code without enabling features:
# Deploy with feature disabled
from feature_flags import is_enabled
@app.route("/api/users")
def get_users():
if is_enabled("new_user_endpoint"):
return new_get_users() # New implementation
else:
return old_get_users() # Old implementation
# Configuration
# feature_flags.yaml
features:
new_user_endpoint:
enabled: false # Deploy code, don't enable yet
rollout_percentage: 0
# Gradual rollout
# 1. Deploy code with flag disabled
# 2. Enable for 5% of users
# 3. Monitor metrics
# 4. Increase to 25%, 50%, 100%
# 5. Remove flag after full rollout
- name: Deploy new version
id: deploy
run: |
kubectl set image deployment/myapp myapp:${{ github.sha }}
kubectl rollout status deployment/myapp
- name: Monitor deployment
id: monitor
run: |
# Check error rates for 5 minutes
ERROR_RATE=$(check_error_rate)
if [ $ERROR_RATE -gt 5 ]; then
echo "Error rate too high: $ERROR_RATE%"
exit 1
fi
- name: Rollback on failure
if: failure()
run: |
echo "Deployment failed, rolling back"
kubectl rollout undo deployment/myapp
# Send alert to team
curl -X POST $SLACK_WEBHOOK \
-d '{"text":"Deployment rolled back due to high error rate"}'
Do's:
Don'ts:
Slow Build (15 minutes):
- name: Install dependencies
run: npm install # Downloads every time
- name: Run all tests
run: npm test # Runs everything
Fast Build (5 minutes):
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci # Faster than npm install
- name: Run unit tests (fast)
run: npm run test:unit
- name: Run integration tests (parallel)
run: npm run test:integration -- --parallel
# E2E tests run only on main branch
- name: Run E2E tests
if: github.ref == 'refs/heads/main'
run: npm run test:e2e
❌ Manual Deployment Steps
→ "SSH into server and run deploy.sh"
✅ Fully automated deployment
❌ No Tests in CI
→ "We'll test it manually"
✅ Automated tests on every commit
❌ Long-Running Builds
→ 30+ minute builds
✅ Optimize to < 10 minutes
❌ Ignoring Broken Builds
→ "I'll fix it later"
✅ Fix immediately or revert
❌ Deploying Without Monitoring
→ "Hope it works!"
✅ Monitor metrics and auto-rollback
❌ No Staging Environment
→ Test in production
✅ Deploy to staging first
❌ Big Bang Deployments
→ Deploy everything at once
✅ Incremental rollouts (canary)
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm test
# .gitlab-ci.yml
stages:
- test
- deploy
test:
stage: test
script:
- npm test
deploy:
stage: deploy
script:
- kubectl apply -f k8s/
only:
- main
# .circleci/config.yml
version: 2.1
jobs:
test:
docker:
- image: node:18
steps:
- checkout
- run: npm test
workflows:
test-deploy:
jobs:
- test
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'npm test'
}
}
stage('Deploy') {
steps {
sh 'kubectl apply -f k8s/'
}
}
}
}
Key Metrics:
Deployment Frequency: How often we deploy
Lead Time: Time from commit to production
Change Failure Rate: % of deployments that fail
Mean Time to Recovery: Time to fix production issues
DORA Metrics Targets:
- Elite: Multiple deploys/day, < 1 hour lead time
- High: Weekly to daily deploys, < 1 day lead time
- Medium: Monthly to weekly, 1 week lead time
- Low: < Monthly, > 6 months lead time
Health Checks:
@app.route("/health")
def health():
return {
"status": "healthy",
"version": os.getenv("VERSION"),
"database": check_database(),
"cache": check_cache(),
}
@app.route("/ready")
def readiness():
# Check if app is ready to serve traffic
if not is_database_ready():
return {"status": "not ready"}, 503
return {"status": "ready"}