CI/CD with GitHub and GitHub Actions

Introduction

Continuous Integration (CI) and Continuous Delivery/Deployment (CD) are cornerstones of modern DevOps practices. They automate the software delivery pipeline, enabling faster, more reliable, and frequent releases. This document outlines best practices for implementing CI/CD using GitHub as the SCM platform and GitHub Actions as the automation engine. It also covers integrating security (DevSecOps) and quality assurance (SonarQube) into the pipeline, along with environment promotion strategies using GitHub Environments.

1. CI/CD Best Practices

2. GitHub and GitHub Actions Overview

3. GitHub Actions Workflows: Examples

3.1 Basic CI Workflow (Build & Test)

This workflow triggers on push and pull_request events to the main branch, building the application and running unit tests.

# .github/workflows/ci.yml
name: CI Build and Test

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-and-test:
    runs-on: ubuntu-latest # Use a GitHub-hosted runner

    steps:
      - name: Checkout code
        uses: actions/checkout@v4 # Action to checkout your repository code

      - name: Setup Node.js (Example for a Node.js project)
        uses: actions/setup-node@v4
        with:
          node-version: '18' # Specify your Node.js version

      - name: Install dependencies
        run: npm ci # Use npm ci for clean installs in CI environments

      - name: Run tests
        run: npm test # Execute your test suite

      - name: Build application
        run: npm run build # Build your application (e.g., for deployment artifacts)

      # Example for a Java project (replace Node.js steps)
      # - name: Setup Java JDK
      #   uses: actions/setup-java@v4
      #   with:
      #     distribution: 'temurin'
      #     java-version: '17'
      # - name: Build with Maven
      #   run: mvn clean install

3.2 Automated SonarQube Analysis

Integrate SonarQube for static code analysis to maintain code quality and identify code smells, bugs, and vulnerabilities. This typically runs as part of the CI process.

# .github/workflows/ci.yml (add to existing CI workflow or create a new one)
name: CI Build, Test, and SonarQube

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-test-sonarqube:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          # SonarQube requires a full history for accurate analysis
          fetch-depth: 0

      - name: Setup Java JDK (Required for SonarQube Scanner)
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Cache SonarQube packages
        uses: actions/cache@v4
        with:
          path: ~/.sonar/cache
          key: ${{ runner.os }}-sonar
          restore-keys: ${{ runner.os }}-sonar

      - name: Build with Maven and SonarQube analysis (Example for Java/Maven)
        run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey= -Dsonar.host.url= -Dsonar.token=${{ secrets.SONAR_TOKEN }}
        env:
          # SONAR_TOKEN should be stored as a GitHub Secret in your repository settings
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

      # For JavaScript/TypeScript projects, you might use sonar-scanner-cli
      # - name: Install SonarQube Scanner CLI
      #   run: npm install -g sonarqube-scanner
      # - name: Run SonarQube Scan
      #   run: sonar-scanner -Dsonar.projectKey= -Dsonar.sources=. -Dsonar.host.url= -Dsonar.token=${{ secrets.SONAR_TOKEN }}
      #   env:
      #     SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

Note: Replace <YOUR_SONARQUBE_PROJECT_KEY> and <YOUR_SONARQUBE_URL> with your actual SonarQube project key and server URL. The SONAR_TOKEN must be stored as a GitHub Secret.

3.3 DevSecOps Practices: Trivy for Vulnerability Scanning

Integrating security scanning early in the pipeline is a key DevSecOps practice. Trivy is a popular open-source vulnerability scanner for container images, file systems, and more.

# .github/workflows/ci.yml (add to existing CI workflow or create a new one)
name: CI Build, Test, and Security Scan

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-test-scan:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      # ... (Your build and test steps here) ...

      - name: Build Docker image (if applicable)
        run: docker build -t my-app:latest . # Replace with your image build command

      - name: Run Trivy vulnerability scan on image
        uses: aquasecurity/trivy-action@master # Use the official Trivy GitHub Action
        with:
          image-ref: 'my-app:latest' # Scan the image you just built
          format: 'table' # Output format (table, json, sarif)
          exit-code: '1' # Fail the workflow if vulnerabilities are found (e.g., critical, high)
          severity: 'CRITICAL,HIGH' # Specify severity levels to fail on

      - name: Run Trivy file system scan (alternative/additional scan)
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs' # Scan the file system (your repository code)
          input: '.' # Scan the current directory
          format: 'table'
          exit-code: '1'
          severity: 'CRITICAL,HIGH'

Aqua Security: While Trivy is a standalone tool from Aqua Security, Aqua also offers a comprehensive platform for cloud-native security. For full integration with Aqua Security's enterprise platform (e.g., image assurance policies, runtime protection), you would typically use their dedicated scanner or integrate with their API. This often involves more complex setup beyond a simple GitHub Action example, but the principle remains: integrate security scans early and often.

4. GitHub Environments for Environment Promotion with Approvals

GitHub Environments allow you to define deployment environments (e.g., Development, Staging, Production) and configure environment-specific rules, including required reviewers, wait timers, and environment secrets. This is crucial for controlled promotion through your CD pipeline.

4.1 Setting up GitHub Environments

  1. Navigate to your repository on GitHub.
  2. Go to Settings > Environments.
  3. Click New environment and create environments like development, staging, production.
  4. For each environment, you can configure:
    • Required reviewers: Select specific teams or users who must approve deployments to this environment. Ensure that the checkbox for "Prevent self-review" is checked so that users cannot approve their own deployments.
    • Wait timer: A delay before a job can proceed.
    • Deployment branches: Restrict which branches can deploy to this environment.
    • Environment secrets: Secrets that are only available to jobs targeting this specific environment.

4.2 CD Workflow with Environments and Approvals

This example demonstrates a multi-stage deployment pipeline using environments with approvals.

# .github/workflows/cd.yml
name: CD Deployment Pipeline

on:
  push:
    branches:
      - main # Trigger deployment when code is pushed to main

jobs:
  deploy-to-development:
    runs-on: ubuntu-latest
    environment:
      name: development # Target the 'development' environment
      url: https://dev.your-app.com # Optional: URL for the deployed application

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy to Development
        run: |
          echo "Deploying to Development environment..."
          # Add your deployment commands here (e.g., kubectl apply, serverless deploy, ansible playbook)
          # Example: deploy using a simple script
          echo "Application deployed to Development!"

  deploy-to-staging:
    runs-on: ubuntu-latest
    needs: deploy-to-development # This job runs only after 'deploy-to-development' succeeds
    environment:
      name: staging # Target the 'staging' environment
      url: https://staging.your-app.com # Optional: URL for the deployed application

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy to Staging
        run: |
          echo "Deploying to Staging environment..."
          # Add your deployment commands here
          echo "Application deployed to Staging!"

  deploy-to-production:
    runs-on: ubuntu-latest
    needs: deploy-to-staging # This job runs only after 'deploy-to-staging' succeeds
    environment:
      name: production # Target the 'production' environment
      url: https://your-app.com # Optional: URL for the deployed application

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy to Production
        run: |
          echo "Deploying to Production environment..."
          # Add your deployment commands here
          echo "Application deployed to Production!"

To set up approvals: In your GitHub repository settings, under Environments, click on the staging and production environments. Under "Deployment protection rules," enable "Required reviewers" and select the teams or individuals who must approve deployments to these environments. When a workflow attempts to deploy to these environments, it will pause until the required reviewers approve.


Conclusion

By leveraging GitHub and GitHub Actions for CI/CD, teams can establish robust, automated pipelines that accelerate software delivery while maintaining high standards of quality and security. Integrating tools like SonarQube for code quality and Trivy for vulnerability scanning enables a "shift-left" approach to DevSecOps. Furthermore, GitHub Environments with approval gates provide critical control and governance over deployments to sensitive environments, ensuring a secure and reliable release process.