CI/CD
Continuous Integration & Continuous Deployment: From Code to Production
CI/CD transforms software delivery from a risky, manual process into an automated, reliable pipeline. By automatically building, testing, and deploying code changes, teams can release features faster, catch bugs earlier, and deliver value to users continuously. This automation isn't just about speed—it's about creating a safety net that gives developers confidence to innovate without fear of breaking production.
Rapid Delivery
Deploy changes multiple times per day with confidence
Early Bug Detection
Catch issues in minutes, not days or weeks
Team Efficiency
Free developers from manual deployment tasks
What is CI/CD?
The Restaurant Kitchen Analogy
Imagine a busy restaurant kitchen:
Without CI/CD (Traditional Approach):
- Chef prepares entire meal alone
- No one tastes until it reaches the customer
- If something’s wrong, the whole meal is remade
- One chef = one meal at a time
With CI/CD (Modern Approach):
- Multiple chefs work on different dishes
- Each component is tasted immediately (CI)
- Approved dishes go straight to customers (CD)
- Kitchen runs continuously, serving many orders
Breaking It Down
Continuous Integration (CI): Developers merge code changes frequently (usually several times per day), with each merge triggering automated builds and tests.
Continuous Deployment (CD): Code that passes all tests is automatically deployed to production without manual intervention.
Continuous Delivery: A variation where code is automatically prepared for release but requires manual approval to deploy.
CI/CD Crash Course (30 Minutes)
Your First Pipeline
Let’s build a simple CI/CD pipeline for a Node.js application using GitHub Actions:
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
# Job 1: Continuous Integration
test:
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: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Build application
run: npm run build
# Job 2: Continuous Deployment
deploy:
needs: test # Only run if tests pass
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
# Your deployment commands here
echo "Deploying to production..."
Understanding the Pipeline Flow
- Trigger: Code pushed to main/develop or PR opened
- Checkout: Pipeline gets latest code
- Setup: Install required tools (Node.js)
- Dependencies: Install project dependencies
- Quality Checks: Run linter for code standards
- Tests: Execute automated tests
- Build: Compile/bundle the application
- Deploy: If on main branch and tests pass, deploy
Quick Start Checklist
-
Create
.github/workflows/directory - Add workflow YAML file
- Define trigger events (push, PR, schedule)
- Set up build environment
- Add test commands
- Configure deployment (if ready)
- Commit and push to see it run!
Popular CI/CD Platforms
GitHub Actions
Best for: GitHub-hosted projects, easy integration
# Example: Python app with GitHub Actions
name: Python CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: |
pip install -r requirements.txt
pytest
Recent GitHub Actions Features:
- Larger runners: Up to 64 vCPUs and 256 GB RAM
- GPU runners: NVIDIA GPU support for ML workloads
- Arm64 runners: Native ARM architecture support
- Deployment protection rules: Environment-specific approvals
GitLab CI/CD
Best for: GitLab users, built-in DevOps features
# .gitlab-ci.yml
stages:
- test
- build
- deploy
test:
stage: test
script:
- npm install
- npm test
deploy:
stage: deploy
script:
- npm run build
- npm run deploy
only:
- main
Jenkins
Best for: Complex workflows, self-hosted, plugins
// Jenkinsfile
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mvn clean package'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
}
stage('Deploy') {
steps {
sh './deploy.sh'
}
}
}
}
CircleCI
Best for: Fast builds, Docker support
# .circleci/config.yml
version: 2.1
jobs:
build-and-test:
docker:
- image: cimg/node:18.0
steps:
- checkout
- run: npm install
- run: npm test
- run: npm run build
workflows:
main:
jobs:
- build-and-test
Platform Comparison
| Platform | Pros | Cons | Best Use Case |
|---|---|---|---|
| GitHub Actions | Free for public repos, great integration | Limited free minutes for private | GitHub projects |
| GitLab CI | Complete DevOps platform | Can be complex | Full DevOps lifecycle |
| Jenkins | Highly customizable, mature | Requires maintenance | Enterprise, complex needs |
| CircleCI | Fast, good caching | Can get expensive | Speed-critical projects |
| Travis CI | Simple setup | Less popular now | Legacy projects |
| Azure DevOps | Great for .NET | Microsoft-centric | Windows/Azure shops |
| Buildkite | Hybrid model, scalable | Requires own runners | High-security needs |
| Drone | Container-native, simple | Smaller community | Kubernetes environments |
Pipeline Design Patterns
1. Simple Linear Pipeline
stages:
- build
- test
- deploy
Use when: Starting out, simple projects
2. Parallel Execution
test:
parallel:
- unit-tests:
script: npm run test:unit
- integration-tests:
script: npm run test:integration
- lint:
script: npm run lint
Use when: Tests are independent, need speed
3. Matrix Builds
strategy:
matrix:
node-version: [14, 16, 18]
os: [ubuntu-latest, windows-latest]
Use when: Testing across multiple environments
4. Fan-out/Fan-in
stages:
- build
- parallel-tests # Fan-out: multiple parallel jobs
- aggregate # Fan-in: collect results
- deploy
Use when: Complex test suites, need aggregated results
5. Blue-Green Pipeline
deploy-blue:
script: deploy_to_blue_environment()
smoke-test:
script: test_blue_environment()
switch-traffic:
script: route_traffic_to_blue()
cleanup-green:
script: teardown_green_environment()
Use when: Zero-downtime deployments critical
Testing Strategies in CI/CD
The Testing Pyramid
/\
/ \ E2E Tests (Slow, Few)
/ \
/------\ Integration Tests (Medium)
/ \
/----------\ Unit Tests (Fast, Many)
Unit Tests in CI
// Fast, isolated tests
describe('Calculator', () => {
it('adds two numbers', () => {
expect(add(2, 3)).toBe(5);
});
});
Run on: Every commit Duration: < 5 minutes
Integration Tests
// Test component interactions
describe('API Integration', () => {
it('creates user and sends email', async () => {
const user = await createUser(data);
expect(emailService.sent).toBe(true);
});
});
Run on: Pull requests Duration: 5-15 minutes
End-to-End Tests
// Test complete user flows
describe('Checkout Flow', () => {
it('completes purchase', async () => {
await login();
await addToCart();
await checkout();
expect(orderConfirmation).toBeVisible();
});
});
Run on: Pre-deployment Duration: 15-60 minutes
Performance Tests
performance-test:
script:
- k6 run load-test.js
rules:
- if: $CI_COMMIT_BRANCH == "main"
Security in CI/CD
Secrets Management
Bad Practice:
deploy:
script:
- API_KEY="sk-1234567890" npm run deploy # Never do this!
Good Practice:
deploy:
script:
- npm run deploy
environment:
name: production
secrets:
API_KEY:
from_secret: api_key_production
Security Scanning Pipeline
security-scan:
stage: security
parallel:
- dependency-check:
script:
- npm audit
- pip-audit # Python (replaced safety)
- bundle audit # Ruby
- osv-scanner --recursive . # Google's OSV Scanner
- sast: # Static Application Security Testing
script:
- semgrep --config=auto
- bandit -r src/ # Python
- snyk code test
- container-scan:
script:
- trivy image myapp:latest
- grype myapp:latest # Anchore scanner
- docker scout cves myapp:latest # Docker's native scanner
- secrets-scan:
script:
- gitleaks detect --source=.
- trufflehog filesystem . --json
Security Best Practices
-
Rotate Secrets Regularly
```yaml
- name: Check secret age run: | if [ $(secret_age $SECRET_NAME) -gt 90 ]; then echo “::error::Secret older than 90 days!” exit 1 fi ```
-
Least Privilege Access
deploy: permissions: contents: read deployments: write # Only what's needed, nothing more -
Audit Logs
```yaml
after_script:
- echo “Deployed by $CI_USER at $CI_TIMESTAMP” » audit.log ```
Deployment Strategies
1. Blue-Green Deployment
deploy-blue-green:
steps:
# Deploy to inactive environment
- name: Deploy to Blue
run: |
kubectl set image deployment/app app=myapp:$VERSION -n blue
kubectl wait --for=condition=ready pod -l app=myapp -n blue
# Test new version
- name: Smoke Test Blue
run: ./smoke-test.sh https://blue.example.com
# Switch traffic
- name: Switch to Blue
run: kubectl patch service app -p '{"spec":{"selector":{"env":"blue"}}}'
# Keep green as rollback option
- name: Tag Green for Rollback
run: kubectl label deployment/app version=previous -n green
2. Canary Deployment
canary-deploy:
steps:
# Deploy canary (10% traffic)
- name: Deploy Canary
run: |
kubectl apply -f canary-deployment.yaml
kubectl set image deployment/app-canary app=myapp:$VERSION
# Monitor metrics
- name: Monitor Canary
run: |
for i in {1..10}; do
ERROR_RATE=$(curl -s https://metrics.api/error-rate)
if [ $ERROR_RATE -gt 5 ]; then
echo "High error rate detected!"
exit 1
fi
sleep 60
done
# Promote canary
- name: Promote Canary
run: kubectl set image deployment/app app=myapp:$VERSION
3. Rolling Deployment
rolling-deploy:
steps:
- name: Configure Rolling Update
run: |
kubectl patch deployment app -p '{
"spec": {
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxSurge": 1,
"maxUnavailable": 0
}
}
}
}'
- name: Update Image
run: kubectl set image deployment/app app=myapp:$VERSION
- name: Monitor Rollout
run: kubectl rollout status deployment/app --timeout=10m
4. Feature Flags Deployment
// Deploy code but control feature activation
if (featureFlag.isEnabled('new-checkout-flow')) {
return renderNewCheckout();
} else {
return renderOldCheckout();
}
feature-flag-deploy:
steps:
- name: Deploy with Feature Off
run: |
FEATURE_FLAGS='{"new-checkout-flow": false}' \
npm run deploy
- name: Gradual Rollout
run: |
for percentage in 10 25 50 100; do
./set-feature-flag.sh new-checkout-flow $percentage
sleep 3600 # Monitor for 1 hour
done
Infrastructure as Code Integration
Terraform in CI/CD
terraform-pipeline:
stages:
- validate
- plan
- apply
validate:
script:
- terraform init
- terraform validate
- terraform fmt -check
plan:
script:
- terraform plan -out=tfplan
artifacts:
paths:
- tfplan
apply:
script:
- terraform apply tfplan
when: manual
only:
- main
Ansible Integration
ansible-deploy:
script:
- ansible-playbook -i inventory/production deploy.yml
before_script:
- ansible-galaxy install -r requirements.yml
- ansible-lint playbooks/
Kubernetes GitOps
# Using ArgoCD for GitOps
gitops-sync:
script:
- |
# Update manifest
yq eval '.spec.template.spec.containers[0].image = "myapp:'$VERSION'"' \
-i k8s/deployment.yaml
# Commit changes
git add k8s/
git commit -m "Update app to version $VERSION"
git push origin main
# ArgoCD automatically syncs
Monitoring and Observability
Pipeline Metrics
collect-metrics:
after_script:
- |
# Send metrics to monitoring system
curl -X POST https://metrics.api/pipeline \
-d '{
"pipeline": "$CI_PIPELINE_ID",
"duration": "$CI_PIPELINE_DURATION",
"status": "$CI_JOB_STATUS",
"branch": "$CI_COMMIT_BRANCH"
}'
Key Metrics to Track
- Lead Time: Commit to production
- Deployment Frequency: Deploys per day/week
- MTTR: Mean Time To Recovery
- Change Failure Rate: Failed deploys percentage
Observability Dashboard Example
# Grafana dashboard query
deployment_frequency:
query: |
count(
ci_pipeline_status{status="success", branch="main"}
) by (day)
lead_time_p95:
query: |
histogram_quantile(0.95,
ci_pipeline_duration_seconds_bucket
)
GitOps Practices
The GitOps Workflow
# 1. Developer commits code
git add .
git commit -m "feat: add payment processing"
git push origin feature/payments
# 2. CI pipeline runs tests
ci-pipeline:
- test
- build
- push-image
# 3. Update deployment manifest
update-manifest:
script:
- git clone https://github.com/myorg/k8s-configs
- cd k8s-configs
- yq eval '.image.tag = "'$CI_COMMIT_SHA'"' -i app/values.yaml
- git commit -am "Update app to $CI_COMMIT_SHA"
- git push
# 4. GitOps operator syncs
# ArgoCD/Flux automatically deploys changes
GitOps Best Practices
-
Separate Config Repo
app-code/ # Application source app-config/ # Kubernetes manifests app-secrets/ # Encrypted secrets (using Sealed Secrets/SOPS) -
Environment Branches
main → production/ staging → staging/ develop → development/ -
Automated Rollback
on-failure: script: - git revert HEAD - git push # GitOps operator automatically rolls back
Modern GitOps Tools:
- ArgoCD: Most popular, great UI, multi-cluster support
- Flux v2: GitOps toolkit, native Kubernetes controller
- Rancher Fleet: Multi-cluster GitOps at scale
- Weave GitOps: Enterprise features, policy management
Common Pitfalls and Troubleshooting
1. Flaky Tests
Problem: Tests pass locally but fail in CI
Solutions:
// Bad: Time-dependent test
it('expires after 1 hour', async () => {
await sleep(3600000); // Don't do this!
expect(isExpired()).toBe(true);
});
// Good: Mock time
it('expires after 1 hour', async () => {
const clock = sinon.useFakeTimers();
clock.tick(3600000);
expect(isExpired()).toBe(true);
clock.restore();
});
2. Secret Leaks
Problem: Accidentally committed secrets
Prevention:
pre-commit-check:
script:
- gitleaks detect --source=. --verbose
- detect-secrets scan --all-files
3. Long Build Times
Problem: Pipeline takes hours
Solutions:
# Cache dependencies
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
# Parallel jobs
test:
parallel: 4
script:
- npm run test:chunk:${CI_NODE_INDEX}
# Incremental builds
build:
script:
- npm run build --since=$CI_COMMIT_BEFORE_SHA
4. Environment Drift
Problem: “Works in staging, breaks in production”
Solution:
# Use identical environments
.deploy_template: &deploy_template
image: deploy:v1.2.3
variables:
TERRAFORM_VERSION: "1.5.0"
KUBECTL_VERSION: "1.27.0"
deploy_staging:
<<: *deploy_template
environment: staging
deploy_production:
<<: *deploy_template
environment: production
Real-World Examples
Example 1: E-commerce Platform
Challenge: Deploy updates without affecting active shoppers
Solution:
name: E-commerce Deployment
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# 1. Build and test
- uses: actions/checkout@v3
- run: |
docker build -t shop:$GITHUB_SHA .
docker run shop:$GITHUB_SHA npm test
# 2. Deploy to canary (5% traffic)
- name: Canary Deploy
run: |
kubectl set image deployment/shop-canary \
shop=shop:$GITHUB_SHA -n production
# 3. Monitor metrics
- name: Monitor Canary
run: |
./scripts/monitor-canary.sh --duration=30m \
--error-threshold=1% \
--latency-p99=200ms
# 4. Full rollout
- name: Production Deploy
run: |
kubectl set image deployment/shop \
shop=shop:$GITHUB_SHA -n production
Example 2: Microservices Platform
Challenge: Coordinate deployment of 50+ services
Solution:
# Monorepo CI/CD
name: Microservices Pipeline
on:
push:
branches: [main]
jobs:
detect-changes:
outputs:
services: ${{ steps.filter.outputs.changes }}
steps:
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
auth: services/auth/**
payment: services/payment/**
inventory: services/inventory/**
# ... 47 more services
build-and-deploy:
needs: detect-changes
strategy:
matrix:
service: ${{ fromJson(needs.detect-changes.outputs.services) }}
steps:
- name: Build Service
run: |
cd services/${{ matrix.service }}
docker build -t ${{ matrix.service }}:$GITHUB_SHA .
- name: Deploy Service
run: |
helm upgrade --install ${{ matrix.service }} \
./charts/${{ matrix.service }} \
--set image.tag=$GITHUB_SHA \
--wait --timeout=5m
Example 3: Mobile App Deployment
Challenge: Deploy to multiple app stores with different requirements
Solution:
name: Mobile App Release
on:
push:
tags:
- 'v*'
jobs:
build:
strategy:
matrix:
platform: [ios, android]
steps:
- uses: actions/checkout@v3
- name: Build $
run: |
if [ "$" == "ios" ]; then
fastlane ios build
else
fastlane android build
fi
- name: Run Tests
run: |
if [ "$" == "ios" ]; then
fastlane ios test
else
fastlane android test
fi
deploy:
needs: build
steps:
- name: Deploy to App Store
run: fastlane ios release
- name: Deploy to Play Store
run: fastlane android release
- name: Notify Team
run: |
curl -X POST $SLACK_WEBHOOK \
-d '{"text":"Version $ released to app stores!"}'
Advanced Topics
Self-Hosted Runners
# Setup for high-security environments
self-hosted-runner:
runs-on: [self-hosted, linux, x64, gpu]
container:
image: custom-runner:latest
options: --gpus all
steps:
- name: ML Model Training
run: python train.py --gpu --distributed
Pipeline as Code Libraries
// Jenkins Shared Library
@Library('company-pipeline-lib') _
companyPipeline {
language = 'java'
type = 'microservice'
deployEnvironments = ['dev', 'staging', 'prod']
slackChannel = '#deployments'
}
Multi-Cloud Deployments
multi-cloud-deploy:
strategy:
matrix:
cloud: [aws, azure, gcp]
steps:
- name: Deploy to $
run: |
case "$" in
aws)
terraform apply -var-file=aws.tfvars
;;
azure)
terraform apply -var-file=azure.tfvars
;;
gcp)
terraform apply -var-file=gcp.tfvars
;;
esac
Getting Started Checklist
Week 1: Foundation
- Choose CI/CD platform
- Create first “Hello World” pipeline
- Add basic tests
- Set up notifications
Week 2: Expansion
- Add code quality checks (linting)
- Implement branch protection
- Create staging deployment
- Add security scanning
Week 3: Optimization
- Implement caching
- Parallelize tests
- Add performance tests
- Create deployment rollback
Week 4: Production Ready
- Set up monitoring
- Implement blue-green deployment
- Add compliance checks
- Document runbooks
Resources and Further Learning
Essential Tools
-
Pipeline Syntax Validators:
- GitHub Actions playground
- GitLab CI Lint
- CircleCI Config Validator
-
Security Scanners:
- Snyk (now with AI-powered fixes)
- SonarQube/SonarCloud
- Checkmarx
- GitHub Advanced Security
-
Monitoring:
- Datadog CI Visibility
- New Relic CodeStream
- Grafana Cloud
- OpenTelemetry (standard for observability)
-
GitOps Operators:
- ArgoCD (with ApplicationSets)
- Flux v2
- Crossplane (infrastructure composition)
Books and Courses
- “Continuous Delivery” by Jez Humble (Classic)
- “The DevOps Handbook” by Gene Kim et al.
- “Accelerate” by Nicole Forsgren et al.
- “Modern Software Engineering” by David Farley (2022)
- “The Phoenix Project” & “The Unicorn Project” by Gene Kim
Online Learning
- DevOps with GitLab CI - GitLab’s official course
- GitHub Actions Deep Dive - A Cloud Guru
- Jenkins 2023 Masterclass - Udemy
- CNCF CI/CD with Tekton - Linux Foundation
Community Resources
- CNCF CI/CD Landscape
- DevOps Weekly Newsletter
- CI/CD Collective Forum
Emerging Trends in CI/CD
-
AI-Powered CI/CD
- Predictive test selection
- Automated flaky test detection
- AI-generated pipeline optimizations
- Smart deployment timing
-
Supply Chain Security
- SBOM (Software Bill of Materials) generation
- SLSA compliance automation
- Sigstore for artifact signing
- Dependency attestation
-
Platform Engineering
- Internal Developer Platforms (IDPs)
- Golden paths for deployment
- Self-service infrastructure
- Developer experience metrics
-
Green CI/CD
- Carbon-aware computing
- Energy-efficient build scheduling
- Resource optimization
- Sustainability metrics
Remember: CI/CD is a journey, not a destination. Start simple, measure everything, and continuously improve your pipeline based on what you learn. The goal isn’t perfection—it’s progress.
Related Technology Documentation
- Git Version Control - Version control fundamentals
- Branching Strategies - Git Flow, GitHub Flow, and team workflows
- Docker - Containerization for consistent builds
- Kubernetes - Container orchestration and deployments
- Terraform - Infrastructure as code
See Also
- Git Version Control - Distributed version control system fundamentals
- Docker - Containerization for consistent build environments
- Kubernetes - Container orchestration and automated deployments
- Terraform - Infrastructure as code for automated provisioning
- AWS - Cloud deployment platforms and services
- Cybersecurity - Security best practices for CI/CD pipelines