CI/CD Overview

The Shirinzad E-Commerce Platform uses modern CI/CD practices to automate building, testing, and deploying the application. This ensures code quality, reduces manual errors, and accelerates the development lifecycle.

Key Benefits: Automated testing on every commit, consistent deployments, faster feedback loops, and improved code quality.

CI/CD Goals

  • Automate build and test processes
  • Ensure code quality through automated checks
  • Enable rapid and reliable deployments
  • Maintain consistent environments
  • Reduce manual deployment errors
  • Accelerate time to production

Pipeline Architecture

graph LR subgraph Source Control A[Git Push/PR] end subgraph CI Pipeline B[Restore Dependencies] C[Build Solution] D[Run Unit Tests] E[Code Coverage] F[Code Quality Checks] end subgraph Artifacts G[Docker Images] H[NuGet Packages] I[Test Reports] end subgraph CD Pipeline J[Deploy to Staging] K[Integration Tests] L[Deploy to Production] end A --> B B --> C C --> D D --> E E --> F F --> G F --> H F --> I G --> J J --> K K --> L

Automated CI/CD workflow from code commit to production

GitHub Actions Workflows

1. Build Pipeline

Automatically builds the solution and runs tests on every push and pull request.

Trigger: Push to any branch, Pull Request to master/develop

Pipeline Steps

Step Action Purpose
1. Checkout actions/checkout@v4 Clone repository code
2. Setup .NET actions/setup-dotnet@v4 Install .NET 9.0 SDK
3. Restore dotnet restore Download NuGet packages
4. Build dotnet build --no-restore Compile source code
5. Test dotnet test --no-build Run all unit/integration tests
6. Coverage coverlet.collector Generate code coverage reports
# .github/workflows/build.yml
name: Build and Test

on:
  push:
    branches: [ master, develop, feature/* ]
  pull_request:
    branches: [ master, develop ]

jobs:
  build:
    runs-on: ubuntu-latest

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

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '9.0.x'

    - name: Restore dependencies
      run: dotnet restore

    - name: Build solution
      run: dotnet build --no-restore --configuration Release

    - name: Run tests
      run: dotnet test --no-build --verbosity normal --configuration Release --collect:"XPlat Code Coverage"

    - name: Upload coverage reports
      uses: codecov/codecov-action@v3
      with:
        files: '**/coverage.cobertura.xml'
        fail_ci_if_error: false

2. Test Pipeline with Coverage

Comprehensive testing with code coverage reporting and quality gates.

Test Coverage Configuration

# .github/workflows/test-coverage.yml
name: Test Coverage

on:
  pull_request:
    branches: [ master ]

jobs:
  test-coverage:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '9.0.x'

    - name: Install dependencies
      run: dotnet restore

    - name: Build
      run: dotnet build --no-restore

    - name: Test with coverage
      run: |
        dotnet test \
          --no-build \
          --verbosity normal \
          /p:CollectCoverage=true \
          /p:CoverletOutputFormat=opencover \
          /p:CoverletOutput=./coverage/

    - name: Generate coverage report
      uses: danielpalme/[email protected]
      with:
        reports: '**/coverage.opencover.xml'
        targetdir: 'coveragereport'
        reporttypes: 'HtmlInline;Badges'

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        files: '**/coverage.opencover.xml'
        flags: unittests
        name: codecov-umbrella

    - name: Check coverage threshold
      run: |
        COVERAGE=$(grep -oP 'Line coverage: \K[0-9.]+' coveragereport/index.html)
        if (( $(echo "$COVERAGE < 80" | bc -l) )); then
          echo "Coverage $COVERAGE% is below 80% threshold"
          exit 1
        fi
Coverage Threshold: Minimum 80% line coverage required for pull requests to master

3. Docker Image Build Pipeline

Builds and publishes Docker images for containerized deployments.

# .github/workflows/docker-build.yml
name: Docker Build and Push

on:
  push:
    branches: [ master, develop ]
    tags: [ 'v*.*.*' ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
    - name: Checkout
      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: Extract metadata
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=semver,pattern={{version}}
          type=semver,pattern={{major}}.{{minor}}
          type=sha

    - name: Build and push Docker image
      uses: docker/build-push-action@v5
      with:
        context: .
        file: ./Dockerfile
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        build-args: |
          BUILD_CONFIGURATION=Release

4. Deployment Pipeline

Automated deployment to staging and production environments.

# .github/workflows/deploy.yml
name: Deploy to Environment

on:
  push:
    branches:
      - develop  # Deploy to staging
      - master   # Deploy to production
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy to'
        required: true
        type: choice
        options:
          - staging
          - production

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: ${{ github.ref == 'refs/heads/master' && 'production' || 'staging' }}
      url: ${{ steps.deploy.outputs.url }}

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

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '9.0.x'

    - name: Build application
      run: |
        dotnet restore
        dotnet build --configuration Release --no-restore
        dotnet publish -c Release -o ./publish

    - name: Run database migrations
      env:
        CONNECTION_STRING: ${{ secrets.DB_CONNECTION_STRING }}
      run: |
        cd src/Shirinzad.Shop.DbMigrator
        dotnet run --configuration Release

    - name: Deploy to Azure Web App
      id: deploy
      uses: azure/webapps-deploy@v2
      with:
        app-name: ${{ secrets.AZURE_WEBAPP_NAME }}
        publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
        package: ./publish

    - name: Health check
      run: |
        ENDPOINT="${{ steps.deploy.outputs.url }}/health"
        for i in {1..10}; do
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" $ENDPOINT)
          if [ $STATUS -eq 200 ]; then
            echo "Health check passed"
            exit 0
          fi
          echo "Attempt $i failed, retrying..."
          sleep 10
        done
        echo "Health check failed after 10 attempts"
        exit 1

5. Code Quality Checks

Automated code quality analysis and security scanning.

# .github/workflows/code-quality.yml
name: Code Quality

on:
  pull_request:
    branches: [ master, develop ]

jobs:
  code-analysis:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0  # Full history for better analysis

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '9.0.x'

    - name: Restore dependencies
      run: dotnet restore

    - name: Build
      run: dotnet build --no-restore

    # Code formatting check
    - name: Check code formatting
      run: dotnet format --verify-no-changes --verbosity diagnostic

    # Security scan
    - name: Run security scan
      uses: security-code-scan/security-code-scan-action@v1

    # Dependency vulnerability check
    - name: Check for vulnerabilities
      run: dotnet list package --vulnerable --include-transitive

    # Static code analysis
    - name: Run static analysis
      run: |
        dotnet tool install --global dotnet-sonarscanner
        dotnet sonarscanner begin \
          /k:"shirinzad-shop" \
          /o:"your-org" \
          /d:sonar.host.url="https://sonarcloud.io" \
          /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
        dotnet build
        dotnet sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"

Database Migration Automation

Automated database migrations ensure database schema is always in sync with the application code.

Migration Strategy

sequenceDiagram participant Dev as Developer participant Git as Git Repository participant CI as CI Pipeline participant DB as Database participant App as Application Dev->>Git: Push code with new migration Git->>CI: Trigger build pipeline CI->>CI: Build application CI->>CI: Run tests CI->>DB: Apply migrations DB-->>CI: Migrations applied CI->>App: Deploy application App->>DB: Connect to updated schema

Migration Execution

# Migration script in deployment pipeline
#!/bin/bash

echo "Starting database migration..."

# Set connection string from environment
export ConnectionStrings__Default="${DB_CONNECTION_STRING}"

# Navigate to migrator project
cd src/Shirinzad.Shop.DbMigrator

# Run migrations
dotnet run --configuration Release

# Check exit code
if [ $? -eq 0 ]; then
    echo "Database migration completed successfully"
else
    echo "Database migration failed"
    exit 1
fi

Migration Best Practices

  • Always test migrations in staging first
  • Create backup before applying migrations
  • Use transactions for data migrations
  • Keep migrations idempotent
  • Add rollback scripts for critical changes
  • Monitor migration execution time

Environment-Specific Configuration

Development

Database LocalDB
Cache In-Memory
Logging Console + Debug
HTTPS Self-signed cert
Secrets User secrets

Staging

Database Azure SQL
Cache Azure Redis
Logging App Insights
HTTPS Valid SSL cert
Secrets Azure Key Vault

Production

Database Azure SQL (HA)
Cache Azure Redis (Premium)
Logging App Insights + Seq
HTTPS Valid SSL cert
Secrets Azure Key Vault

Configuration Management

  • Environment variables
  • appsettings.{Environment}.json
  • Azure Key Vault integration
  • User secrets (development)
  • Configuration validation on startup

Secrets Management

Secure handling of sensitive configuration data across environments.

GitHub Secrets Configuration

Secret Name Description Used In
DB_CONNECTION_STRING Database connection string Deployment, Migrations
AZURE_WEBAPP_NAME Azure App Service name Deployment
AZURE_WEBAPP_PUBLISH_PROFILE Publishing credentials Deployment
REDIS_CONNECTION_STRING Redis cache connection Deployment
SONAR_TOKEN SonarCloud authentication Code quality checks
CODECOV_TOKEN Codecov authentication Coverage reporting
Security Best Practice: Never commit secrets to source control. Use environment variables and secure secret stores.

Local Development Secrets

# Initialize user secrets
cd src/Shirinzad.Shop.Web
dotnet user-secrets init

# Set secrets
dotnet user-secrets set "ConnectionStrings:Default" "Server=...;Database=Shop;..."
dotnet user-secrets set "Redis:Configuration" "localhost:6379"
dotnet user-secrets set "Email:ApiKey" "your-api-key"

# List all secrets
dotnet user-secrets list

Pipeline Triggers

Automatic Triggers

Event Branches Pipelines Actions
Push master, develop, feature/* Build, Test Build, test, code quality
Pull Request master, develop Build, Test, Quality Full validation + coverage
Push to develop develop Build, Deploy Deploy to staging
Push to master master Build, Deploy Deploy to production
Tag push v*.*.* Build, Docker, Release Build release artifacts
Schedule master Security scan Daily security checks

Manual Triggers

# Manual deployment workflow
name: Manual Deploy

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        type: choice
        options:
          - staging
          - production
      skip_tests:
        description: 'Skip tests (not recommended)'
        type: boolean
        default: false
      migrate_database:
        description: 'Run database migrations'
        type: boolean
        default: true

Branch Protection Rules

Enforce code quality and review processes through branch protection.

Master Branch Protection

  • Require pull request reviews (minimum 1 approval)
  • Require status checks to pass before merging
  • Require branches to be up to date before merging
  • Require conversation resolution before merging
  • Require linear history (no merge commits)
  • Prohibit force pushes
  • Restrict who can push to master

Required Status Checks

Check Description Required For
Build Successful build All branches
Tests All tests passing All branches
Code Coverage Minimum 80% coverage master only
Code Quality SonarCloud quality gate master only
Security Scan No critical vulnerabilities master only

Develop Branch Protection

  • Require pull request reviews
  • Require build and test checks
  • Allow merge commits
  • Prohibit force pushes

Alternative CI/CD Platforms

Azure DevOps Pipelines

Example Azure Pipelines configuration for the Shirinzad platform.

# azure-pipelines.yml
trigger:
  branches:
    include:
      - master
      - develop
      - feature/*

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'
  dotnetVersion: '9.0.x'

stages:
- stage: Build
  displayName: 'Build and Test'
  jobs:
  - job: Build
    displayName: 'Build Solution'
    steps:
    - task: UseDotNet@2
      displayName: 'Install .NET SDK'
      inputs:
        version: $(dotnetVersion)

    - task: DotNetCoreCLI@2
      displayName: 'Restore packages'
      inputs:
        command: 'restore'

    - task: DotNetCoreCLI@2
      displayName: 'Build solution'
      inputs:
        command: 'build'
        arguments: '--configuration $(buildConfiguration) --no-restore'

    - task: DotNetCoreCLI@2
      displayName: 'Run tests'
      inputs:
        command: 'test'
        arguments: '--configuration $(buildConfiguration) --no-build --collect:"XPlat Code Coverage"'
        publishTestResults: true

    - task: PublishCodeCoverageResults@1
      displayName: 'Publish coverage'
      inputs:
        codeCoverageTool: 'Cobertura'
        summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'

- stage: Deploy
  displayName: 'Deploy to Staging'
  dependsOn: Build
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
  jobs:
  - deployment: DeployStaging
    displayName: 'Deploy to Staging Environment'
    environment: 'staging'
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureWebApp@1
            displayName: 'Deploy to Azure Web App'
            inputs:
              azureSubscription: 'Azure-Service-Connection'
              appName: 'shirinzad-staging'
              package: '$(Pipeline.Workspace)/**/*.zip'

Azure DevOps Features

  • Integrated with Azure services
  • Built-in artifact management
  • Release management with approvals
  • Extensive marketplace extensions
  • Private agent pools support
  • Advanced deployment strategies

Jenkins Pipeline

Example Jenkinsfile for building and deploying the application.

// Jenkinsfile
pipeline {
    agent any

    environment {
        DOTNET_CLI_HOME = '/tmp/dotnet'
        DOTNET_VERSION = '9.0'
    }

    stages {
        stage('Setup') {
            steps {
                sh 'curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version ${DOTNET_VERSION}'
            }
        }

        stage('Restore') {
            steps {
                sh 'dotnet restore'
            }
        }

        stage('Build') {
            steps {
                sh 'dotnet build --configuration Release --no-restore'
            }
        }

        stage('Test') {
            steps {
                sh 'dotnet test --configuration Release --no-build --logger trx --collect:"XPlat Code Coverage"'
            }
            post {
                always {
                    mstest testResultsFile: '**/*.trx'
                    publishCoverage adapters: [coberturaAdapter('**/coverage.cobertura.xml')]
                }
            }
        }

        stage('Publish') {
            when {
                branch 'master'
            }
            steps {
                sh 'dotnet publish -c Release -o ./publish'
                archiveArtifacts artifacts: 'publish/**/*', fingerprint: true
            }
        }

        stage('Deploy to Staging') {
            when {
                branch 'develop'
            }
            steps {
                script {
                    // Deploy to staging server
                    sh '''
                        scp -r ./publish/* user@staging-server:/var/www/shirinzad/
                        ssh user@staging-server 'systemctl restart shirinzad'
                    '''
                }
            }
        }

        stage('Deploy to Production') {
            when {
                branch 'master'
            }
            steps {
                input message: 'Deploy to production?', ok: 'Deploy'
                script {
                    // Deploy to production server
                    sh '''
                        scp -r ./publish/* user@prod-server:/var/www/shirinzad/
                        ssh user@prod-server 'systemctl restart shirinzad'
                    '''
                }
            }
        }
    }

    post {
        success {
            emailext(
                subject: "Build Successful: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: "Build ${env.BUILD_NUMBER} completed successfully.",
                to: "[email protected]"
            )
        }
        failure {
            emailext(
                subject: "Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: "Build ${env.BUILD_NUMBER} failed. Check console output.",
                to: "[email protected]"
            )
        }
    }
}

Jenkins Advantages

  • Self-hosted with full control
  • Extensive plugin ecosystem
  • Flexible pipeline configuration
  • Support for complex workflows
  • Integration with various tools
  • Cost-effective for large teams

Monitoring and Notifications

Pipeline Monitoring

  • Build Status: Real-time build status badges
  • Test Results: Test trend analysis
  • Code Coverage: Coverage trend tracking
  • Deployment History: Deployment logs and rollback capability
  • Performance Metrics: Build duration tracking

Notification Channels

  • Email: Build and deployment results
  • Slack: Real-time pipeline updates
  • Teams: Integration with Microsoft Teams
  • GitHub: Status checks on pull requests
  • SMS: Critical production failures

Slack Notification Example

# Add to workflow for Slack notifications
- name: Notify Slack on Success
  if: success()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "✅ Build #${{ github.run_number }} succeeded",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Build Successful*\nBranch: ${{ github.ref }}\nCommit: ${{ github.sha }}\nAuthor: ${{ github.actor }}"
            }
          }
        ]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

CI/CD Best Practices

Pipeline Design

  • Keep pipelines fast (under 10 minutes)
  • Fail fast on critical errors
  • Use caching for dependencies
  • Parallelize independent jobs
  • Use matrix builds for multiple targets
  • Separate build and deployment stages

Security

  • Store secrets securely
  • Use least privilege for service accounts
  • Scan for vulnerabilities in dependencies
  • Sign artifacts and containers
  • Audit pipeline changes
  • Rotate credentials regularly

Testing

  • Run tests on every commit
  • Maintain high code coverage (80%+)
  • Use test result trends for quality
  • Separate unit and integration tests
  • Include smoke tests in deployment
  • Automate regression testing

Deployment

  • Deploy to staging first
  • Use blue-green or canary deployments
  • Implement automatic rollback
  • Validate deployments with health checks
  • Keep deployment scripts in version control
  • Document deployment procedures

Troubleshooting Common Issues

Issue Possible Cause Solution
Build fails on restore NuGet package not found Check NuGet.Config, verify package sources
Tests timeout Long-running tests Increase timeout, optimize tests, use mocks
Migration fails Database connection issue Verify connection string, check firewall rules
Deployment hangs Health check failing Check application logs, verify endpoints
Docker build slow No layer caching Optimize Dockerfile, use build cache
Secrets not available Missing environment variables Configure GitHub secrets, check permissions

Related Documentation