Infrastructure as Code with ARM Templates and Bicep: Part 6 – CI/CD Integration and Advanced Deployment

Infrastructure as Code with ARM Templates and Bicep: Part 6 – CI/CD Integration and Advanced Deployment

This entry is part 6 of 7 in the series Infrastructure as Code templates using ARM and Bicep

This part focuses on integrating Bicep templates with CI/CD pipelines, implementing advanced deployment strategies, and establishing robust testing and validation practices for enterprise-scale Infrastructure as Code.

What You’ll Learn

  • Advanced CI/CD pipeline patterns
  • Blue-green and canary deployment strategies
  • Automated testing and validation
  • Secret management and security practices
  • Multi-environment deployment strategies

GitHub Actions Workflow

name: Infrastructure as Code - Bicep

on:
  push:
    branches: [main, develop]
    paths: ['infrastructure/**']

jobs:
  lint-and-validate:
    name: Lint and Validate Templates
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Azure Login
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Lint Bicep Files
        run: |
          cd infrastructure/bicep
          for file in $(find . -name "*.bicep"); do
            az bicep build --file "$file" --stdout > /dev/null
          done

      - name: Validate Templates
        run: |
          for env in dev staging prod; do
            az deployment sub validate \
              --location eastus \
              --template-file infrastructure/bicep/main.bicep \
              --parameters @infrastructure/parameters/$env.bicepparam
          done

  deploy-dev:
    name: Deploy to Development
    needs: lint-and-validate
    environment: development
    steps:
      - name: Deploy Infrastructure
        run: |
          az deployment sub create \
            --location eastus \
            --template-file infrastructure/bicep/main.bicep \
            --parameters @infrastructure/parameters/dev.bicepparam

Blue-Green Deployment Pattern

// Blue-Green deployment pattern for App Services

@description('Application name')
param appName string

@description('Deployment slot to deploy to')
@allowed(['blue', 'green', 'staging'])
param deploymentSlot string = 'staging'

// App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
  name: 'asp-${appName}'
  location: resourceGroup().location
  sku: {
    name: 'S1'
    capacity: 1
  }
}

// Main App Service
resource appService 'Microsoft.Web/sites@2023-01-01' = {
  name: 'app-${appName}'
  location: resourceGroup().location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
    siteConfig: {
      healthCheckPath: '/health'
      appSettings: [
        {
          name: 'DEPLOYMENT_SLOT'
          value: 'production'
        }
      ]
    }
  }
}

// Deployment Slot
resource deploymentSlotResource 'Microsoft.Web/sites/slots@2023-01-01' = {
  parent: appService
  name: deploymentSlot
  location: resourceGroup().location
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      healthCheckPath: '/health'
      appSettings: [
        {
          name: 'DEPLOYMENT_SLOT'
          value: deploymentSlot
        }
      ]
    }
  }
}

// Outputs
output appServiceUrl string = 'https://${appService.properties.defaultHostName}'
output deploymentSlotUrl string = 'https://${deploymentSlotResource.properties.defaultHostName}'

Automated Testing Framework

// Integration test template

@description('Target environment to test')
param targetEnvironment string

// Test execution using Azure Container Instances
resource integrationTestContainer 'Microsoft.ContainerInstance/containerGroups@2023-05-01' = {
  name: 'integration-tests-${targetEnvironment}'
  location: resourceGroup().location
  properties: {
    osType: 'Linux'
    restartPolicy: 'Never'
    containers: [
      {
        name: 'test-runner'
        properties: {
          image: 'mcr.microsoft.com/azure-cli:latest'
          command: [
            '/bin/bash'
            '-c'
            '''
            echo "Starting integration tests..."
            
            # Health Check Test
            APP_URL="https://app-webapp-${targetEnvironment}.azurewebsites.net"
            HEALTH_URL="$APP_URL/health"
            
            if curl -f -s $HEALTH_URL; then
              echo "✅ Health check passed"
            else
              echo "❌ Health check failed"
              exit 1
            fi
            
            echo "All tests passed!"
            '''
          ]
        }
      }
    ]
  }
}

Secret Management

// Key Vault for secure secret management

@description('Application name')
param appName string

@description('Environment name')
param environment string

// User-assigned managed identity
resource appIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
  name: 'id-${appName}-${environment}'
  location: resourceGroup().location
}

// Key Vault
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
  name: 'kv-${appName}-${environment}-${uniqueString(resourceGroup().id)}'
  location: resourceGroup().location
  properties: {
    tenantId: tenant().tenantId
    sku: {
      family: 'A'
      name: environment == 'prod' ? 'premium' : 'standard'
    }
    enableRbacAuthorization: true
    enablePurgeProtection: environment == 'prod'
    publicNetworkAccess: environment == 'prod' ? 'Disabled' : 'Enabled'
  }
}

// RBAC assignment for application
resource appIdentitySecretUserRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(keyVault.id, appIdentity.id)
  scope: keyVault
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
    principalId: appIdentity.properties.principalId
    principalType: 'ServicePrincipal'
  }
}

// Outputs
output keyVaultId string = keyVault.id
output appIdentityId string = appIdentity.id

Best Practices

  • Security: Use managed identities, implement RBAC, enable Key Vault
  • Deployment: Use infrastructure as code, implement automated testing
  • Monitoring: Implement logging, set up alerts, use Application Insights
  • Operations: Implement backups, use auto-scaling, proper tagging

What’s Next?

In Part 7, we’ll explore enterprise governance patterns, compliance automation, and advanced cost optimization strategies.


This is Part 6 of our 7-part series on Infrastructure as Code with ARM Templates and Bicep.

Navigate<< Infrastructure as Code with ARM Templates and Bicep: Part 5 – Advanced Bicep PatternsInfrastructure as Code with ARM Templates and Bicep: Part 7 – Enterprise Governance and Compliance >>

Written by:

265 Posts

View All Posts
Follow Me :
How to whitelist website on AdBlocker?

How to whitelist website on AdBlocker?

  1. 1 Click on the AdBlock Plus icon on the top right corner of your browser
  2. 2 Click on "Enabled on this site" from the AdBlock Plus option
  3. 3 Refresh the page and start browsing the site