cicdworkshop

WORKSHOP

WORKSHOPLast updated: 1/31/2026

CI/CD: Hands-On Workshop

Practical, step-by-step exercises to master continuous integration and deployment. Estimated 120-150 minutes.


Learning Objectives

By the end of this workshop, you will be able to: ✅ Create and configure CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins)
✅ Implement automated testing and code quality checks
✅ Deploy applications using multiple strategies (blue-green, canary)
✅ Manage secrets and secure credentials
✅ Monitor pipeline performance and health
✅ Troubleshoot pipeline failures
✅ Implement rollback procedures
✅ Compare CI/CD platforms


Part 1: GitHub Actions Setup (20 minutes)

Task 1.1: Create Basic Workflow

Objective: Set up first CI/CD pipeline with GitHub Actions

# Step 1: Clone repository
git clone https://github.com/your-org/sample-app
cd sample-app

# Step 2: Create workflow directory
mkdir -p .github/workflows

# Step 3: Create workflow file
cat > .github/workflows/ci.yml << 'EOF'
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 Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run lint
        run: npm run lint
      
      - name: Run tests
        run: npm test -- --coverage
      
      - name: Build
        run: npm run build
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info

# Step 4: Commit and push
git add .github/workflows/ci.yml
git commit -m "Add GitHub Actions CI pipeline"
git push origin main

# Expected output: Workflow triggered automatically
# View results: https://github.com/your-org/sample-app/actions

Verification: ✅ Workflow appears in Actions tab and runs successfully


Task 1.2: Add Docker Build

Objective: Automate Docker image building and pushing

# Step 1: Create GitHub token
# Go to: Settings → Developer settings → Personal access tokens → Tokens (classic)
# Grant: repo, write:packages

# Step 2: Create Docker build workflow
cat > .github/workflows/docker.yml << 'EOF'
name: Build and Push Docker Image

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

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: |
            ${{ secrets.DOCKER_USERNAME }}/sample-app:latest
            ${{ secrets.DOCKER_USERNAME }}/sample-app:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
EOF

# Step 3: Add secrets to repository
# Go to: Settings → Secrets and variables → Actions → New repository secret
# Add: DOCKER_USERNAME and DOCKER_PASSWORD

git add .github/workflows/docker.yml
git commit -m "Add Docker build workflow"
git push

# Expected output: Docker images pushed to Docker Hub

Verification: ✅ Docker images built and pushed successfully


Part 2: GitLab CI Setup (20 minutes)

Task 2.1: Create GitLab Pipeline

Objective: Configure CI/CD pipeline in GitLab

# Step 1: Create .gitlab-ci.yml
cat > .gitlab-ci.yml << 'EOF'
stages:
  - build
  - test
  - deploy

variables:
  DOCKER_DRIVER: overlay2
  NODE_VERSION: "18"

build:
  stage: build
  image: node:18
  cache:
    paths:
      - node_modules/
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 day

test:
  stage: test
  image: node:18
  cache:
    paths:
      - node_modules/
  script:
    - npm ci
    - npm test -- --coverage
    - npm run lint
  coverage: '/Coverage: \d+\.\d+%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

deploy_staging:
  stage: deploy
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - echo "Deploying to staging..."
    - ./deploy.sh staging
  only:
    - develop

deploy_production:
  stage: deploy
  environment:
    name: production
    url: https://example.com
  script:
    - echo "Deploying to production..."
    - ./deploy.sh production
  only:
    - main
  when: manual
EOF

# Step 2: Commit pipeline
git add .gitlab-ci.yml
git commit -m "Add GitLab CI pipeline"
git push

# Expected output: Pipeline runs automatically

Verification: ✅ Pipeline executes all stages successfully


Task 2.2: Container Registry

Objective: Build and push images to GitLab Container Registry

# Step 1: Create registry integration
cat >> .gitlab-ci.yml << 'EOF'

build_image:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest
  only:
    - main
EOF

git add .gitlab-ci.yml
git commit -m "Add container registry build"
git push

# Expected output: Images pushed to GitLab Container Registry
# View: Project → Deployments → Container Registry

Verification: ✅ Container images appear in registry


Part 3: Jenkins Setup (25 minutes)

Task 3.1: Create Jenkinsfile

Objective: Set up Jenkins pipeline using Jenkinsfile

# Step 1: Create Jenkinsfile
cat > Jenkinsfile << 'EOF'
pipeline {
    agent any
    
    environment {
        NODE_ENV = 'production'
        CI = 'true'
        BUILD_ID = "${BUILD_NUMBER}"
    }
    
    options {
        timestamps()
        timeout(time: 1, unit: 'HOURS')
        buildDiscarder(logRotator(numToKeepStr: '10'))
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }
        
        stage('Lint') {
            steps {
                sh 'npm run lint'
            }
        }
        
        stage('Test') {
            steps {
                sh 'npm test -- --coverage'
                junit 'test-results/**/*.xml'
            }
        }
        
        stage('Build') {
            steps {
                sh 'npm run build'
                archiveArtifacts artifacts: 'dist/**/*', fingerprint: true
            }
        }
        
        stage('Deploy Staging') {
            when {
                branch 'develop'
            }
            steps {
                sh './deploy.sh staging'
            }
        }
        
        stage('Deploy Production') {
            when {
                branch 'main'
            }
            input {
                message "Deploy to production?"
                ok "Deploy"
            }
            steps {
                sh './deploy.sh production'
            }
        }
    }
    
    post {
        always {
            junit 'test-results/**/*.xml'
            publishHTML([
                reportDir: 'coverage',
                reportFiles: 'index.html',
                reportName: 'Coverage Report'
            ])
        }
        failure {
            mail to: 'team@example.com',
                 subject: "Build Failed: ${env.JOB_NAME}",
                 body: "Build failed: ${env.BUILD_URL}"
        }
        success {
            echo "Build successful!"
        }
    }
}
EOF

# Step 2: Configure Jenkins job
# - Go to Jenkins UI (http://jenkins:8080)
# - Create new job → Pipeline
# - Configuration → Pipeline → Definition: Pipeline script from SCM
# - SCM: Git → Repository URL: (your repo)
# - Script Path: Jenkinsfile

git add Jenkinsfile
git commit -m "Add Jenkinsfile"
git push

# Expected output: Jenkins job runs automatically on push

Verification: ✅ Jenkinsfile executes all pipeline stages


Task 3.2: Configure Jenkins Agents

Objective: Set up distributed builds with Jenkins agents

# Step 1: Configure agent labels
# Jenkins UI → Manage Jenkins → Manage Nodes and Clouds
# Create new agent: agent-docker
# Remote root directory: /var/jenkins_agents/agent-docker

# Step 2: Update Jenkinsfile to use agents
cat > Jenkinsfile << 'EOF'
pipeline {
    agent {
        node {
            label 'agent-docker'
            customWorkspace '/var/jenkins_agents/workspace'
        }
    }
    
    stages {
        stage('Build') {
            agent {
                docker {
                    image 'node:18'
                    label 'agent-docker'
                }
            }
            steps {
                sh 'npm ci && npm run build'
            }
        }
    }
}
EOF

git add Jenkinsfile
git commit -m "Add agent configuration"
git push

Verification: ✅ Builds run on specified agent


Part 4: Deployment Strategies (25 minutes)

Task 4.1: Blue-Green Deployment

Objective: Implement zero-downtime deployments

# Step 1: Create deployment script
cat > deploy-blue-green.sh << 'EOF'
#!/bin/bash
set -e

CURRENT=$(curl -s https://app.example.com/version | jq -r '.environment')

if [ "$CURRENT" = "blue" ]; then
    STANDBY="green"
    PORT=8002
else
    STANDBY="blue"
    PORT=8001
fi

echo "Deploying to $STANDBY environment..."

# Deploy
docker pull $IMAGE:$BUILD_ID
docker stop app-$STANDBY || true
docker run -d \
  --name app-$STANDBY \
  -p $PORT:3000 \
  $IMAGE:$BUILD_ID

# Health check
sleep 10
for i in {1..30}; do
    if curl -f http://localhost:$PORT/health > /dev/null 2>&1; then
        echo "Health check passed"
        break
    fi
    sleep 2
done

# Switch traffic
sudo /opt/switch-traffic.sh $PORT

echo "Deployment complete"
EOF

chmod +x deploy-blue-green.sh

# Step 2: Add to pipeline
git add deploy-blue-green.sh
git commit -m "Add blue-green deployment"
git push

Verification: ✅ Deployments with zero downtime


Task 4.2: Canary Deployment

Objective: Test changes on subset of users first

# Step 1: Create canary deployment
cat > deploy-canary.sh << 'EOF'
#!/bin/bash

kubectl set image deployment/app-canary app=$IMAGE:$BUILD_ID --record
kubectl wait --for=condition=ready pod -l app=app-canary --timeout=300s

# Route 10% traffic to canary
cat | kubectl apply -f - << YAML
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: app
spec:
  hosts:
  - app
  http:
  - route:
    - destination:
        host: app-stable
      weight: 90
    - destination:
        host: app-canary
      weight: 10
YAML

echo "Canary deployed with 10% traffic"

# Monitor
sleep 120
ERROR_RATE=$(kubectl logs -l app=app-canary --tail=1000 | grep "error" | wc -l)

if [ $ERROR_RATE -gt 100 ]; then
    kubectl rollout undo deployment/app-canary
    exit 1
fi

# Promote
kubectl set image deployment/app app=$IMAGE:$BUILD_ID --record
kubectl wait --for=condition=ready pod -l app=app --timeout=300s

echo "Canary promoted to production"
EOF

chmod +x deploy-canary.sh
git add deploy-canary.sh
git commit -m "Add canary deployment"
git push

Verification: ✅ Canary deployment with traffic gradation


Part 5: Testing & Quality (20 minutes)

Task 5.1: Add Test Coverage

Objective: Implement comprehensive testing

# Step 1: Create test configuration
cat > jest.config.js << 'EOF'
module.exports = {
  testEnvironment: 'node',
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'cobertura'],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};
EOF

# Step 2: Run tests locally
npm test -- --coverage

# Step 3: Generate SonarQube report
npm test -- --coverage --reporters=default --reporters=jest-junit

# Step 4: Commit
git add jest.config.js
git commit -m "Add test coverage configuration"
git push

Verification: ✅ Tests pass with 80%+ coverage


Task 5.2: Code Quality Checks

Objective: Enforce code quality standards

# Step 1: Setup linting
npm install --save-dev eslint prettier

# Step 2: Add to CI pipeline
cat >> .github/workflows/ci.yml << 'EOF'
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: SonarQube Scan
        uses: SonarSource/sonarcloud-github-action@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
      
      - name: Dependency Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'sample-app'
          path: '.'
          format: 'JSON'
EOF

git add .github/workflows/ci.yml
git commit -m "Add code quality checks"
git push

Verification: ✅ Quality gates passing


Part 6: Secrets & Security (20 minutes)

Task 6.1: Manage Secrets

Objective: Securely store and use sensitive data

# Step 1: Add secrets to GitHub
# Go to: Settings → Secrets and variables → Actions
# Add: DB_PASSWORD, API_KEY, JWT_SECRET

# Step 2: Use secrets in workflow
cat > .github/workflows/deploy.yml << 'EOF'
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy with secrets
        env:
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
          API_KEY: ${{ secrets.API_KEY }}
          JWT_SECRET: ${{ secrets.JWT_SECRET }}
        run: |
          echo "DATABASE_PASSWORD=$DB_PASSWORD" > .env
          echo "API_KEY=$API_KEY" >> .env
          ./deploy.sh
      
      - name: Cleanup
        run: rm -f .env
EOF

git add .github/workflows/deploy.yml
git commit -m "Add secure deployment"
git push

# Expected: Secrets masked in logs

Verification: ✅ Secrets not exposed in logs


Task 6.2: SAST Scanning

Objective: Detect security vulnerabilities

# Step 1: Add dependency scanning
cat >> .github/workflows/ci.yml << 'EOF'
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Run security scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
      
      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'
EOF

git add .github/workflows/ci.yml
git commit -m "Add security scanning"
git push

# Expected: Security vulnerabilities detected and reported

Verification: ✅ Security scan completes without critical issues


Validation Checklist

  • GitHub Actions pipeline created and running
  • Docker images built and pushed
  • GitLab CI pipeline configured
  • Container registry images verified
  • Jenkins Jenkinsfile created
  • Jenkins pipeline executing stages
  • Jenkins agents configured
  • Blue-green deployment tested
  • Canary deployment working
  • Test coverage > 80%
  • Code quality checks passing
  • Secrets configured securely
  • Security scanning enabled
  • All pipelines successful

Summary

Completed Tasks: ✅ Created CI pipelines (GitHub Actions, GitLab CI, Jenkins)
✅ Automated Docker image builds
✅ Implemented multiple deployment strategies
✅ Added comprehensive testing
✅ Configured security scanning
✅ Managed secrets securely
✅ Set up monitoring and alerts

Time Spent: ~120 minutes (6 parts × 20 minutes)

Platform Comparison:

  • GitHub Actions: Best for GitHub-native projects, simplest setup
  • GitLab CI: Best for complete DevOps platform, strong container support
  • Jenkins: Best for complex pipelines, highest flexibility

Next Steps:

  • Review CONCEPT.md for advanced patterns
  • Read RUNBOOK.md for production procedures
  • Explore BUSINESS.md for ROI analysis
  • Implement additional stages (security, performance testing)
  • Set up multi-environment deployments

Document Version: 1.0
Last Updated: January 31, 2026
Level: Beginner to Intermediate
Estimated Duration: 120-150 minutes