What is CI/CD?
BeginnerContinuous Integration (CI) automatically builds and tests code on every commit. Continuous Delivery (CD) automatically deploys tested code to staging/production environments.
CI/CD Benefits
- Faster feedback — Catch bugs within minutes of committing
- Reliable releases — Automated, repeatable deployment process
- Reduced risk — Small, frequent deployments instead of large releases
- Developer productivity — Automate manual build and deploy steps
GitHub Actions
Beginner# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Run tests
run: npm test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v4
build-and-push:
needs: lint-and-test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
deploy:
needs: build-and-push
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: |
echo "Deploying ${{ github.sha }} to production..."
# Add your deployment commands here
Jenkins
Intermediate// Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'registry.example.com'
IMAGE_NAME = 'myapp'
KUBECONFIG = credentials('kubeconfig-prod')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build & Test') {
steps {
sh 'npm ci'
sh 'npm run lint'
sh 'npm test'
}
post {
always {
junit 'test-results/**/*.xml'
publishHTML(target: [
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Build Docker Image') {
steps {
script {
def image = docker.build("${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}")
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-creds') {
image.push()
image.push('latest')
}
}
}
}
stage('Deploy to Staging') {
steps {
sh """
kubectl set image deployment/myapp \
myapp=${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER} \
-n staging
kubectl rollout status deployment/myapp -n staging --timeout=300s
"""
}
}
stage('Deploy to Production') {
when { branch 'main' }
input { message 'Deploy to production?' }
steps {
sh """
kubectl set image deployment/myapp \
myapp=${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER} \
-n production
kubectl rollout status deployment/myapp -n production --timeout=300s
"""
}
}
}
post {
failure {
slackSend channel: '#deployments',
color: 'danger',
message: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
}
success {
slackSend channel: '#deployments',
color: 'good',
message: "Build SUCCESS: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
}
}
}
GitLab CI
Intermediate# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
test:
stage: test
image: node:20-alpine
cache:
paths:
- node_modules/
script:
- npm ci
- npm run lint
- npm test
coverage: '/Statements\s*:\s*(\d+\.?\d*)%/'
artifacts:
reports:
junit: test-results.xml
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
only:
- main
deploy_production:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/myapp myapp=$IMAGE_TAG -n production
- kubectl rollout status deployment/myapp -n production
environment:
name: production
url: https://app.example.com
when: manual
only:
- main
Deployment Strategies
Intermediate| Strategy | Description | Risk |
|---|---|---|
| Rolling Update | Gradually replace old pods with new ones | Low |
| Blue/Green | Run two identical environments, switch traffic | Low |
| Canary | Route small % of traffic to new version first | Very Low |
| Recreate | Kill all old pods, start new ones (downtime) | High |
| A/B Testing | Route based on user attributes or headers | Low |
ArgoCD & GitOps
AdvancedGitOps is a paradigm where Git is the single source of truth for infrastructure and application state. ArgoCD continuously syncs your Kubernetes cluster with your Git repository.
# Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# ArgoCD Application manifest
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp-k8s-manifests.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
maxDuration: 3m
factor: 2
Pipeline Security
Advanced Security is Non-Negotiable
CI/CD pipelines have access to production systems and secrets. A compromised pipeline can compromise everything.
- Secret management — Never hardcode secrets; use vault solutions
- Image scanning — Scan Docker images for CVEs in the pipeline
- SAST/DAST — Static and dynamic application security testing
- Signed commits — Require GPG-signed commits
- Branch protection — Require reviews before merging to main
- Least privilege — Pipeline service accounts should have minimal permissions
- Audit logs — Track all pipeline executions and deployments
- Supply chain security — Pin action versions, verify checksums