Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

CI/CD Patterns

A comprehensive guide to Continuous Integration and Continuous Deployment patterns, branching strategies, and deployment techniques for modern software delivery.


Overview

CI/CD (Continuous Integration/Continuous Deployment) patterns define how code flows from development to production. These patterns establish workflows for integrating changes, automating tests, managing artifacts, and deploying applications safely and reliably.

Continuous DeploymentContinuous IntegrationCode CommitBuildUnit TestsIntegration TestsCode AnalysisArtifact CreationDeploy to StagingAcceptance TestsDeploy to ProductionMonitoringFeedbackContinuous DeploymentContinuous IntegrationCode CommitBuildUnit TestsIntegration TestsCode AnalysisArtifact CreationDeploy to StagingAcceptance TestsDeploy to ProductionMonitoringFeedback

Trunk-Based Development

Key Concepts

Concept Description
Single Branch All developers commit to a single main branch (trunk)
Short-Lived Branches Feature branches last hours to days, not weeks
Feature Flags Toggle incomplete features off in production
Continuous Integration Multiple integrations per day per developer
Release Branches Optional short-lived branches for releases

Common Patterns

mainfeature-afeature-brelease-1.0main-1feat-1main-2feat-2feat-3main-3rel-1v1.0mainfeature-afeature-brelease-1.0main-1feat-1main-2feat-2feat-3main-3rel-1v1.0

Examples

Feature Flag Implementation:

# Feature flag configuration
features:
  new_checkout_flow:
    enabled: false
    rollout_percentage: 0
    allowed_users:
      - beta_testers

  enhanced_search:
    enabled: true
    rollout_percentage: 100

Branch Protection Rules:

# GitHub branch protection
branches:
  - name: main
    protection:
      required_status_checks:
        strict: true
        contexts:
          - build
          - test
          - lint
      required_pull_request_reviews:
        required_approving_review_count: 1
      enforce_admins: true

Trunk-Based CI Pipeline:

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/

test:
  stage: test
  script:
    - npm run test:unit
    - npm run test:integration
  coverage: '/Coverage: \d+\.\d+%/'

deploy:
  stage: deploy
  script:
    - ./deploy.sh
  only:
    - main
  when: manual

GitFlow and GitHub Flow

Key Concepts

Strategy Branches Best For
GitFlow main, develop, feature/, release/, hotfix/* Scheduled releases, multiple versions
GitHub Flow main, feature/* Continuous deployment, web apps
GitLab Flow main, feature/*, environment branches Environment-based deployments

GitFlow Diagram

maindevelopfeature/loginrelease/1.0hotfix/1.0.1initdev-1login-1login-2rel-prepv1.0dev-2fix-1v1.0.1maindevelopfeature/loginrelease/1.0hotfix/1.0.1initdev-1login-1login-2rel-prepv1.0dev-2fix-1v1.0.1

GitHub Flow Diagram

mainfeature/new-apifeature/ui-updatemain-1api-1api-2main-2deployui-1main-3deploymainfeature/new-apifeature/ui-updatemain-1api-1api-2main-2deployui-1main-3deploy

Examples

GitFlow Branch Commands:

# Start a new feature
git checkout develop
git checkout -b feature/user-authentication

# Finish feature
git checkout develop
git merge --no-ff feature/user-authentication
git branch -d feature/user-authentication

# Start a release
git checkout develop
git checkout -b release/1.2.0

# Finish release
git checkout main
git merge --no-ff release/1.2.0
git tag -a v1.2.0 -m "Release 1.2.0"
git checkout develop
git merge --no-ff release/1.2.0
git branch -d release/1.2.0

# Hotfix
git checkout main
git checkout -b hotfix/1.2.1
# ... fix the issue ...
git checkout main
git merge --no-ff hotfix/1.2.1
git tag -a v1.2.1 -m "Hotfix 1.2.1"
git checkout develop
git merge --no-ff hotfix/1.2.1
git branch -d hotfix/1.2.1

GitHub Flow Workflow:

# .github/workflows/github-flow.yml
name: GitHub Flow

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: npm ci && npm run build
      - name: Test
        run: npm test

  deploy:
    needs: build-and-test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to Production
        run: ./scripts/deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Pipeline Stages (Build, Test, Deploy)

Key Concepts

Stage Purpose Activities
Build Compile and package Dependency resolution, compilation, asset bundling
Test Verify quality Unit tests, integration tests, security scans
Deploy Release to environment Configuration, deployment, smoke tests

Pipeline Architecture

Deploy StageDeploy to DevSmoke TestsDeploy to StagingUATDeploy to ProdTest StageUnit TestsIntegration TestsE2E TestsSecurity ScanCode QualityBuild StageCheckout CodeInstall DependenciesCompile/TranspileCreate ArtifactsDeploy StageDeploy to DevSmoke TestsDeploy to StagingUATDeploy to ProdTest StageUnit TestsIntegration TestsE2E TestsSecurity ScanCode QualityBuild StageCheckout CodeInstall DependenciesCompile/TranspileCreate Artifacts

Examples

Multi-Stage Jenkins Pipeline:

// Jenkinsfile
pipeline {
    agent any

    environment {
        DOCKER_REGISTRY = 'registry.example.com'
        APP_NAME = 'myapp'
    }

    stages {
        stage('Build') {
            steps {
                sh 'npm ci'
                sh 'npm run build'
                sh "docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} ."
            }
        }

        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'npm run test:unit'
                    }
                }
                stage('Integration Tests') {
                    steps {
                        sh 'npm run test:integration'
                    }
                }
                stage('Security Scan') {
                    steps {
                        sh 'npm audit --audit-level=high'
                        sh 'trivy image ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER}'
                    }
                }
            }
        }

        stage('Push Artifact') {
            steps {
                sh "docker push ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER}"
            }
        }

        stage('Deploy to Staging') {
            steps {
                sh "kubectl set image deployment/${APP_NAME} ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} -n staging"
            }
        }

        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            input {
                message "Deploy to production?"
                ok "Deploy"
            }
            steps {
                sh "kubectl set image deployment/${APP_NAME} ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} -n production"
            }
        }
    }

    post {
        always {
            junit '**/test-results/*.xml'
            archiveArtifacts artifacts: 'dist/**/*', fingerprint: true
        }
        failure {
            slackSend channel: '#deployments', color: 'danger', message: "Build failed: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
        }
    }
}

GitHub Actions Multi-Stage Pipeline:

name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - name: Set version
        id: version
        run: echo "version=$(date +%Y%m%d)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT

      - name: Build application
        run: |
          npm ci
          npm run build

      - name: Build Docker image
        run: docker build -t myapp:${{ steps.version.outputs.version }} .

      - name: Save Docker image
        run: docker save myapp:${{ steps.version.outputs.version }} > myapp.tar

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: docker-image
          path: myapp.tar

  test:
    needs: build
    runs-on: ubuntu-latest
    strategy:
      matrix:
        test-type: [unit, integration, e2e]
    steps:
      - uses: actions/checkout@v4
      - name: Run ${{ matrix.test-type }} tests
        run: npm run test:${{ matrix.test-type }}

  security-scan:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run security audit
        run: npm audit --audit-level=high
      - name: Run SAST
        uses: github/codeql-action/analyze@v2

  deploy-staging:
    needs: [test, security-scan]
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: docker-image
      - name: Deploy to staging
        run: ./scripts/deploy.sh staging

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: docker-image
      - name: Deploy to production
        run: ./scripts/deploy.sh production

Artifact Management

Key Concepts

Concept Description
Immutable Artifacts Build once, deploy many times
Versioning Semantic versioning or build numbers
Retention Policies Automatic cleanup of old artifacts
Checksums Verify artifact integrity
Metadata Track build info, dependencies, provenance

Artifact Flow

DeploymentArtifact RegistryBuild EnvironmentSource CodeBuild ProcessArtifactContainer RegistryPackage RegistryBinary RepositoryDevStagingProductionDeploymentArtifact RegistryBuild EnvironmentSource CodeBuild ProcessArtifactContainer RegistryPackage RegistryBinary RepositoryDevStagingProduction

Examples

Docker Image Management:

# Build and push with multiple tags
name: Build and Push

on:
  push:
    branches: [main]
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Artifact Retention in GitLab:

# .gitlab-ci.yml
build:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
      - coverage/
    reports:
      junit: junit.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
    expire_in: 1 week
    when: always

release:
  stage: release
  script:
    - npm run build:release
  artifacts:
    paths:
      - dist/
    expire_in: never
  only:
    - tags

Nexus/Artifactory Upload:

#!/bin/bash
# Upload artifact to Nexus

NEXUS_URL="https://nexus.example.com/repository/maven-releases"
GROUP_ID="com.example"
ARTIFACT_ID="myapp"
VERSION="1.2.3"
PACKAGING="jar"

curl -v -u "${NEXUS_USER}:${NEXUS_PASS}" \
  --upload-file "target/${ARTIFACT_ID}-${VERSION}.${PACKAGING}" \
  "${NEXUS_URL}/${GROUP_ID//.//}/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}.${PACKAGING}"

# Verify checksum
sha256sum "target/${ARTIFACT_ID}-${VERSION}.${PACKAGING}" > checksum.txt
curl -v -u "${NEXUS_USER}:${NEXUS_PASS}" \
  --upload-file checksum.txt \
  "${NEXUS_URL}/${GROUP_ID//.//}/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}.${PACKAGING}.sha256"

Helm Chart Repository:

# Chart.yaml
apiVersion: v2
name: myapp
description: My application Helm chart
type: application
version: 1.2.3
appVersion: "2.0.0"

# Package and push
# helm package ./myapp
# helm push myapp-1.2.3.tgz oci://registry.example.com/charts

Environment Promotion Strategies

Key Concepts

Strategy Description Use Case
Sequential Dev → Test → Staging → Prod Traditional workflow
Parallel Deploy to multiple envs simultaneously Fast feedback
Ring-based Gradual rollout to user segments Large-scale systems
GitOps Git as source of truth for env state Kubernetes deployments

Promotion Flow

PassFailPassFailApprovedRejectedPassFailApprovedRejectedArtifact CreatedAutomated TestsDeploy to DevNotify TeamDev TestsDeploy to QAQA ApprovalDeploy to StagingUAT & PerformanceManual ApprovalDeploy to ProductionMonitor & ValidatePassFailPassFailApprovedRejectedPassFailApprovedRejectedArtifact CreatedAutomated TestsDeploy to DevNotify TeamDev TestsDeploy to QAQA ApprovalDeploy to StagingUAT & PerformanceManual ApprovalDeploy to ProductionMonitor & Validate

Examples

GitOps with ArgoCD:

# Application manifest for ArgoCD
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-production
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/k8s-manifests.git
    targetRevision: HEAD
    path: environments/production/myapp
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

---
# Promotion script
#!/bin/bash
# promote.sh - Promote image to next environment

SOURCE_ENV=$1
TARGET_ENV=$2
APP=$3

# Get current image from source
IMAGE=$(kubectl get deployment $APP -n $SOURCE_ENV -o jsonpath='{.spec.template.spec.containers[0].image}')

# Update target environment manifests
cd k8s-manifests/environments/$TARGET_ENV/$APP
kustomize edit set image app=$IMAGE
git add .
git commit -m "Promote $APP to $TARGET_ENV: $IMAGE"
git push

echo "Promotion triggered. ArgoCD will sync automatically."

Environment Configuration Management:

# kustomization.yaml for each environment

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml

# environments/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
namePrefix: staging-
namespace: staging
patches:
  - path: replica-patch.yaml
configMapGenerator:
  - name: app-config
    behavior: merge
    literals:
      - LOG_LEVEL=debug
      - FEATURE_FLAGS=all

# environments/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
namePrefix: prod-
namespace: production
patches:
  - path: replica-patch.yaml
  - path: resources-patch.yaml
configMapGenerator:
  - name: app-config
    behavior: merge
    literals:
      - LOG_LEVEL=info
      - FEATURE_FLAGS=stable

Spinnaker Pipeline Configuration:

{
  "name": "Production Deployment",
  "stages": [
    {
      "type": "bake",
      "name": "Bake AMI",
      "baseOs": "ubuntu",
      "package": "myapp",
      "regions": ["eu-west-1"]
    },
    {
      "type": "deploy",
      "name": "Deploy to Staging",
      "clusters": [{
        "account": "staging",
        "application": "myapp",
        "strategy": "redblack",
        "capacity": {"min": 2, "max": 4, "desired": 2}
      }]
    },
    {
      "type": "manualJudgment",
      "name": "Approve Production",
      "instructions": "Verify staging deployment before proceeding"
    },
    {
      "type": "deploy",
      "name": "Deploy to Production",
      "clusters": [{
        "account": "production",
        "application": "myapp",
        "strategy": "redblack",
        "capacity": {"min": 4, "max": 10, "desired": 6}
      }]
    }
  ]
}

Rollback Procedures

Key Concepts

Type Description Speed Risk
Instant Rollback Switch traffic to previous version Seconds Low
Redeploy Previous Deploy previous artifact again Minutes Low
Database Rollback Revert schema/data changes Minutes-Hours High
Feature Flag Disable feature without deploy Seconds Low

Rollback Decision Flow

CriticalHighMediumYesNoYesNoSimpleComplexIssue DetectedSeverity?Immediate RollbackCan fix quickly?InvestigateHot FixRoot cause found?Fix complexity?Feature Flag OffVerify RollbackPost-MortemCriticalHighMediumYesNoYesNoSimpleComplexIssue DetectedSeverity?Immediate RollbackCan fix quickly?InvestigateHot FixRoot cause found?Fix complexity?Feature Flag OffVerify RollbackPost-Mortem

Examples

Kubernetes Rollback:

#!/bin/bash
# rollback.sh - Kubernetes deployment rollback

NAMESPACE=$1
DEPLOYMENT=$2
REVISION=${3:-0}  # 0 means previous version

# Check rollout history
kubectl rollout history deployment/$DEPLOYMENT -n $NAMESPACE

# Rollback to previous version
if [ "$REVISION" -eq 0 ]; then
    kubectl rollout undo deployment/$DEPLOYMENT -n $NAMESPACE
else
    kubectl rollout undo deployment/$DEPLOYMENT -n $NAMESPACE --to-revision=$REVISION
fi

# Wait for rollback to complete
kubectl rollout status deployment/$DEPLOYMENT -n $NAMESPACE --timeout=300s

# Verify pods are healthy
kubectl get pods -n $NAMESPACE -l app=$DEPLOYMENT

echo "Rollback completed successfully"

Blue/Green Rollback with Nginx:

#!/bin/bash
# blue-green-rollback.sh

CURRENT=$(readlink /etc/nginx/sites-enabled/app)

if [[ $CURRENT == *"blue"* ]]; then
    NEW="green"
    OLD="blue"
else
    NEW="blue"
    OLD="green"
fi

# Switch to previous version
ln -sfn /etc/nginx/sites-available/app-$NEW /etc/nginx/sites-enabled/app
nginx -t && nginx -s reload

echo "Rolled back from $OLD to $NEW"

Database Rollback with Flyway:

#!/bin/bash
# db-rollback.sh

# Check current version
flyway info

# Rollback last migration (requires undo scripts)
flyway undo

# Or rollback to specific version
# flyway undo -target=5

# Verify
flyway info

Automated Rollback in GitHub Actions:

name: Deploy with Auto-Rollback

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Get current version
        id: current
        run: |
          CURRENT=$(kubectl get deployment myapp -o jsonpath='{.spec.template.spec.containers[0].image}')
          echo "version=$CURRENT" >> $GITHUB_OUTPUT

      - name: Deploy new version
        run: |
          kubectl set image deployment/myapp myapp=myregistry/myapp:${{ github.sha }}
          kubectl rollout status deployment/myapp --timeout=300s

      - name: Run smoke tests
        id: smoke
        continue-on-error: true
        run: |
          ./scripts/smoke-tests.sh

      - name: Rollback on failure
        if: steps.smoke.outcome == 'failure'
        run: |
          echo "Smoke tests failed, rolling back..."
          kubectl set image deployment/myapp myapp=${{ steps.current.outputs.version }}
          kubectl rollout status deployment/myapp --timeout=300s
          exit 1

Blue/Green and Canary Deployments

Key Concepts

Strategy Description Traffic Split Rollback Time
Blue/Green Two identical environments, instant switch 0% or 100% Instant
Canary Gradual traffic shift to new version Configurable % Fast
Rolling Replace instances incrementally Gradual Moderate
A/B Testing Route based on user attributes By segment Fast

Deployment Strategies Comparison

Rolling Deploymentv1 v1 v1v2 v1 v1v2 v2 v1v2 v2 v2Canary Deploymentv1 - 100%v1 90% / v2 10%v1 50% / v2 50%v2 - 100%Blue/Green DeploymentBlue v1 - ActiveGreen v2 - IdleSwitch TrafficGreen v2 - ActiveRolling Deploymentv1 v1 v1v2 v1 v1v2 v2 v1v2 v2 v2Canary Deploymentv1 - 100%v1 90% / v2 10%v1 50% / v2 50%v2 - 100%Blue/Green DeploymentBlue v1 - ActiveGreen v2 - IdleSwitch TrafficGreen v2 - Active

Examples

Blue/Green with Kubernetes Service:

# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
        - name: myapp
          image: myregistry/myapp:1.0.0

---
# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
        - name: myapp
          image: myregistry/myapp:2.0.0

---
# service.yaml - Switch by changing selector
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
    version: blue  # Change to 'green' to switch
  ports:
    - port: 80
      targetPort: 8080

Canary with Istio:

# VirtualService for canary routing
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
    - myapp
  http:
    - match:
        - headers:
            canary:
              exact: "true"
      route:
        - destination:
            host: myapp
            subset: canary
    - route:
        - destination:
            host: myapp
            subset: stable
          weight: 90
        - destination:
            host: myapp
            subset: canary
          weight: 10

---
# DestinationRule
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: myapp
spec:
  host: myapp
  subsets:
    - name: stable
      labels:
        version: stable
    - name: canary
      labels:
        version: canary

Argo Rollouts Canary:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp
spec:
  replicas: 10
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myregistry/myapp:2.0.0
  strategy:
    canary:
      steps:
        - setWeight: 10
        - pause: {duration: 5m}
        - setWeight: 30
        - pause: {duration: 5m}
        - setWeight: 50
        - pause: {duration: 5m}
        - setWeight: 80
        - pause: {duration: 5m}
      analysis:
        templates:
          - templateName: success-rate
        startingStep: 1
        args:
          - name: service-name
            value: myapp

---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  args:
    - name: service-name
  metrics:
    - name: success-rate
      interval: 1m
      successCondition: result[0] >= 0.95
      provider:
        prometheus:
          address: http://prometheus:9090
          query: |
            sum(rate(http_requests_total{service="{{args.service-name}}",status=~"2.*"}[5m])) /
            sum(rate(http_requests_total{service="{{args.service-name}}"}[5m]))

AWS CodeDeploy Canary:

# appspec.yml
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: "arn:aws:ecs:region:account:task-definition/myapp:2"
        LoadBalancerInfo:
          ContainerName: "myapp"
          ContainerPort: 8080

Hooks:
  - BeforeInstall: "scripts/before_install.sh"
  - AfterInstall: "scripts/after_install.sh"
  - AfterAllowTestTraffic: "scripts/test_traffic.sh"
  - AfterAllowTraffic: "scripts/validation.sh"

Best Practices for CI/CD Pipelines

Key Concepts

Category Best Practice Benefit
Speed Parallelise tests Faster feedback
Reliability Idempotent builds Consistent results
Security Scan dependencies Prevent vulnerabilities
Observability Comprehensive logging Easier debugging
Efficiency Cache dependencies Reduced build time

Pipeline Design Patterns

Best Practice PatternsFail FastRun quick testsfirstCache EverythingDependencies,builds, imagesParalleliseIndependent testsrun concurrentlyImmutable ArtifactsBuild once, deploymanyInfrastructure asCodeVersion controlledconfigsBest Practice PatternsFail FastRun quick testsfirstCache EverythingDependencies,builds, imagesParalleliseIndependent testsrun concurrentlyImmutable ArtifactsBuild once, deploymanyInfrastructure asCodeVersion controlledconfigs

Examples

Optimised Pipeline with Caching:

# .github/workflows/optimised.yml
name: Optimised CI/CD

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run test -- --shard=${{ matrix.shard }}/4

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build with cache
        uses: docker/build-push-action@v5
        with:
          context: .
          push: false
          tags: myapp:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Security-Focused Pipeline:

name: Secure Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * *'  # Daily security scan

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Dependency audit
        run: npm audit --audit-level=high

      - name: SAST scan
        uses: github/codeql-action/analyze@v2

      - name: Secret detection
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./

      - name: Container scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'myapp:${{ github.sha }}'
          format: 'sarif'
          output: 'trivy-results.sarif'

      - name: Upload scan results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

Pipeline Observability:

# Datadog CI visibility
name: Observable Pipeline

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure Datadog CI
        run: |
          npm install -g @datadog/datadog-ci

      - name: Run tests with tracing
        env:
          DD_API_KEY: ${{ secrets.DD_API_KEY }}
          DD_SITE: datadoghq.eu
        run: |
          datadog-ci junit upload --service myapp ./test-results/

      - name: Tag pipeline
        run: |
          datadog-ci tag --level pipeline \
            --tags "env:ci,version:${{ github.sha }}"

Clean Code Pipeline Configuration:

# Reusable workflow
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      version:
        required: true
        type: string
    secrets:
      DEPLOY_TOKEN:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - name: Deploy to ${{ inputs.environment }}
        run: |
          echo "Deploying version ${{ inputs.version }} to ${{ inputs.environment }}"
          ./scripts/deploy.sh ${{ inputs.environment }} ${{ inputs.version }}
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

# Caller workflow
# .github/workflows/main.yml
name: Main Pipeline

on:
  push:
    branches: [main]

jobs:
  build:
    # ... build steps ...
    outputs:
      version: ${{ steps.version.outputs.version }}

  deploy-staging:
    needs: build
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: staging
      version: ${{ needs.build.outputs.version }}
    secrets:
      DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}

  deploy-production:
    needs: [build, deploy-staging]
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
      version: ${{ needs.build.outputs.version }}
    secrets:
      DEPLOY_TOKEN: ${{ secrets.PROD_DEPLOY_TOKEN }}

Quick Reference

Pattern When to Use Key Benefit
Trunk-based Small teams, continuous deployment Fast integration
GitFlow Scheduled releases, multiple versions Clear release process
GitHub Flow Simple projects, web apps Simplicity
Blue/Green Zero-downtime requirements Instant rollback
Canary Risk mitigation, large user base Gradual validation
Rolling Resource constraints Minimal extra resources
GitOps Kubernetes environments Declarative, auditable

Common CI/CD Tools

Category Tools
CI Servers Jenkins, GitLab CI, GitHub Actions, CircleCI, Travis CI
CD/GitOps ArgoCD, Flux, Spinnaker, Harness
Artifact Storage Nexus, Artifactory, GitHub Packages, ECR, GCR
Container Orchestration Kubernetes, ECS, Cloud Run
Feature Flags LaunchDarkly, Split, Unleash, Flagsmith

Pipeline Stage Checklist

Build Stage:
[ ] Checkout code
[ ] Install dependencies (cached)
[ ] Compile/transpile
[ ] Create artifacts
[ ] Tag with version

Test Stage:
[ ] Unit tests
[ ] Integration tests
[ ] E2E tests
[ ] Security scan
[ ] Code quality analysis
[ ] Performance tests

Deploy Stage:
[ ] Environment configuration
[ ] Database migrations
[ ] Deploy application
[ ] Smoke tests
[ ] Health checks
[ ] Monitoring verification

Common Issues and Solutions

Pipeline Failures

Issue Cause Solution
Flaky tests Race conditions, external dependencies Isolate tests, use mocks, retry logic
Slow builds No caching, sequential execution Enable caching, parallelise stages
OOM errors Insufficient resources Increase memory, optimise builds
Timeout failures Long-running processes Increase timeout, split into stages
Dependency conflicts Version mismatches Lock file, consistent environments

Deployment Failures

Issue Cause Solution
Failed health checks Misconfigured probes Adjust probe timing, check endpoints
Database migration errors Schema conflicts Test migrations, use versioning
Secret not found Missing environment config Verify secret exists in target env
Insufficient permissions RBAC misconfiguration Review service account permissions
Resource quota exceeded Cluster limits Request quota increase, optimise resources

Rollback Issues

Issue Cause Solution
Cannot rollback DB No backward migrations Always create undo scripts
Cached old version CDN/browser cache Cache invalidation strategy
Stale connections Connection pooling Graceful shutdown, connection draining
Lost transactions Mid-flight requests Queue-based processing, retry logic

Example Troubleshooting

Debugging Failed Deployments:

#!/bin/bash
# diagnose-deployment.sh

NAMESPACE=$1
DEPLOYMENT=$2

echo "=== Deployment Status ==="
kubectl get deployment $DEPLOYMENT -n $NAMESPACE

echo "=== Recent Events ==="
kubectl get events -n $NAMESPACE --sort-by='.lastTimestamp' | grep $DEPLOYMENT

echo "=== Pod Status ==="
kubectl get pods -n $NAMESPACE -l app=$DEPLOYMENT

echo "=== Pod Logs ==="
for pod in $(kubectl get pods -n $NAMESPACE -l app=$DEPLOYMENT -o name); do
    echo "--- $pod ---"
    kubectl logs $pod -n $NAMESPACE --tail=50
done

echo "=== Describe Deployment ==="
kubectl describe deployment $DEPLOYMENT -n $NAMESPACE

Handling Flaky Tests:

# Jest configuration for retry
module.exports = {
  // Retry failed tests
  testRetries: 2,

  // Increase timeout for slow tests
  testTimeout: 30000,

  // Run tests in band for better isolation
  runInBand: true,

  // Fail on first error in CI
  bail: process.env.CI ? 1 : 0,
};

Pipeline Timeout Handling:

# GitHub Actions timeout configuration
jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Long running task
        timeout-minutes: 10
        run: ./scripts/build.sh

      - name: Continue on timeout
        if: failure() && steps.previous.conclusion == 'cancelled'
        run: echo "Previous step timed out"

Related Topics

The following topics would complement this CI/CD Patterns cheatsheet:

  1. GitOps and ArgoCD - Deep dive into GitOps workflows, ArgoCD configuration, and declarative infrastructure management for Kubernetes environments.

  2. Container Security and Scanning - Comprehensive coverage of container vulnerability scanning, image signing, SBOM generation, and supply chain security practices.

  3. Infrastructure as Code (Terraform/Pulumi) - Patterns for managing infrastructure alongside application code, including state management and drift detection.

  4. Observability and Monitoring - Metrics, logs, and traces for CI/CD pipelines, deployment tracking, and SLO/SLI monitoring with tools like Prometheus and Grafana.

  5. Feature Flag Management - Strategies for implementing feature flags, progressive rollouts, and A/B testing in conjunction with deployment pipelines.

  6. Kubernetes Deployment Strategies - Advanced Kubernetes patterns including operators, custom resources, service mesh integration, and multi-cluster deployments.