{"id":2030,"date":"2025-07-16T06:09:49","date_gmt":"2025-07-16T06:09:49","guid":{"rendered":"https:\/\/www.nicktailor.com\/?p=2030"},"modified":"2025-07-16T06:13:50","modified_gmt":"2025-07-16T06:13:50","slug":"building-production-ready-release-pipelines-in-azure-a-step-by-step-guide-using-arm-templates","status":"publish","type":"post","link":"https:\/\/nicktailor.com\/tech-blog\/building-production-ready-release-pipelines-in-azure-a-step-by-step-guide-using-arm-templates\/","title":{"rendered":"Building Production-Ready Release Pipelines in Azure: A Step-by-Step Guide using Arm Templates"},"content":{"rendered":"<p>Creating enterprise-grade release pipelines in Azure requires a comprehensive understanding of Azure DevOps services, proper configuration, and adherence to production best practices. This detailed guide will walk you through building a robust CI\/CD pipeline that deploys applications to Azure App Services with slot-based deployments for zero-downtime releases.<\/p>\n<h2>Architecture Overview<\/h2>\n<p>Our production pipeline will deploy a .NET web application to Azure App Service using deployment slots for blue\/green deployments. The pipeline includes multiple environments (development, staging, production) with automated testing, security scanning, and manual approval gates.<\/p>\n<div style=\"background-color: #f0f0f0; padding: 15px; border-radius: 6px; margin: 20px 0; text-align: center; font-family: monospace; border-left: 4px solid #0066cc;\"><strong>Pipeline Flow:<\/strong><br \/>\nAzure Repos \u2192 Build Pipeline (Azure Pipelines) \u2192 Dev Deployment \u2192 Automated Tests \u2192 Staging Deployment \u2192 Security Scan \u2192 Manual Approval \u2192 Production Deployment (Slot Swap) \u2192 Post-Deployment Monitoring<\/div>\n<h2>Prerequisites<\/h2>\n<p>Before starting, ensure you have:<\/p>\n<ul>\n<li>Azure subscription with sufficient permissions<\/li>\n<li>Azure DevOps organization and project<\/li>\n<li>.NET application in Azure Repos (or GitHub)<\/li>\n<li>Understanding of Azure Resource Manager (ARM) templates<\/li>\n<li>Azure CLI installed locally<\/li>\n<\/ul>\n<h2>Understanding Azure Deployment Slots<\/h2>\n<p>Before diving into infrastructure setup, it&#8217;s crucial to understand <strong>Azure deployment slots<\/strong> &#8211; a key feature that enables zero-downtime deployments and advanced deployment strategies.<\/p>\n<h3>What Are Deployment Slots?<\/h3>\n<p>Azure App Service <strong>deployment slots<\/strong> are live instances of your web application with their own hostnames. Think of them as separate environments that share the same App Service plan but can run different versions of your application.<\/p>\n<ul>\n<li><strong>Production slot<\/strong>: Your main application (e.g., <code style=\"background-color: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', Consolas, monospace; font-size: 14px;\">myapp.azurewebsites.net<\/code>)<\/li>\n<li><strong>Staging slot<\/strong>: A separate instance (e.g., <code style=\"background-color: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', Consolas, monospace; font-size: 14px;\">myapp-staging.azurewebsites.net<\/code>)<\/li>\n<li><strong>Additional slots<\/strong>: Canary, testing, or feature-specific environments<\/li>\n<\/ul>\n<h3>Why Use Deployment Slots?<\/h3>\n<div style=\"background-color: #f0f8ff; padding: 15px; border-radius: 6px; margin: 20px 0; border-left: 4px solid #0066cc;\"><strong>Zero-Downtime Deployment Process:<\/strong><br \/>\n1. Deploy new version to staging slot<br \/>\n2. Test the staging slot thoroughly<br \/>\n3. <strong>Swap<\/strong> staging and production slots instantly<br \/>\n4. If issues arise, swap back immediately (rollback)<\/div>\n<p><strong>Key Benefits:<\/strong><\/p>\n<ul>\n<li><strong>Zero-downtime deployments<\/strong>: Instant traffic switching<\/li>\n<li><strong>Blue\/green deployments<\/strong>: Run two versions simultaneously<\/li>\n<li><strong>A\/B testing<\/strong>: Route percentage of traffic to different versions<\/li>\n<li><strong>Warm-up validation<\/strong>: Test in production environment before going live<\/li>\n<li><strong>Quick rollbacks<\/strong>: Instant revert if problems occur<\/li>\n<\/ul>\n<div style=\"background-color: #fff3cd; border: 1px solid #ffeeba; color: #856404; padding: 15px; border-radius: 6px; margin: 20px 0;\"><strong>Important:<\/strong> Deployment slots are only available in Standard, Premium, and Isolated App Service plan tiers. They&#8217;re not available in Free or Basic tiers.<\/div>\n<h2>Step 1: Infrastructure Setup &#8211; Choose Your Approach<\/h2>\n<p>Azure offers two primary Infrastructure as Code (IaC) approaches for managing resources including deployment slots:<\/p>\n<ul>\n<li><strong>ARM Templates\/Bicep<\/strong>: Azure&#8217;s native IaC solution<\/li>\n<li><strong>Terraform<\/strong>: Multi-cloud infrastructure management tool<\/li>\n<\/ul>\n<h3>Option A: ARM Templates\/Bicep (Recommended for Azure-only environments)<\/h3>\n<p>Create an ARM template (<code style=\"background-color: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', Consolas, monospace; font-size: 14px;\">infrastructure\/main.bicep<\/code>) for your infrastructure:<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">param location string = resourceGroup().location\nparam environmentName string\nparam appServicePlanSku string = 'S1'\n\nresource appServicePlan 'Microsoft.Web\/serverfarms@2022-03-01' = {\n  name: 'asp-myapp-${environmentName}'\n  location: location\n  sku: {\n    name: appServicePlanSku\n    tier: 'Standard'\n  }\n  properties: {\n    reserved: false\n  }\n}\n\nresource webApp 'Microsoft.Web\/sites@2022-03-01' = {\n  name: 'app-myapp-${environmentName}'\n  location: location\n  properties: {\n    serverFarmId: appServicePlan.id\n    httpsOnly: true\n    siteConfig: {\n      netFrameworkVersion: 'v6.0'\n      defaultDocuments: [\n        'Default.htm'\n        'Default.html'\n        'index.html'\n      ]\n      httpLoggingEnabled: true\n      logsDirectorySizeLimit: 35\n      detailedErrorLoggingEnabled: true\n      appSettings: [\n        {\n          name: 'ASPNETCORE_ENVIRONMENT'\n          value: environmentName\n        }\n        {\n          name: 'ApplicationInsights__ConnectionString'\n          value: applicationInsights.properties.ConnectionString\n        }\n      ]\n    }\n  }\n}\n\n\/\/ Create staging slot for production environment\nresource stagingSlot 'Microsoft.Web\/sites\/slots@2022-03-01' = if (environmentName == 'prod') {\n  parent: webApp\n  name: 'staging'\n  location: location\n  properties: {\n    serverFarmId: appServicePlan.id\n    httpsOnly: true\n    siteConfig: {\n      netFrameworkVersion: 'v6.0'\n      appSettings: [\n        {\n          name: 'ASPNETCORE_ENVIRONMENT'\n          value: 'Staging'\n        }\n        {\n          name: 'ApplicationInsights__ConnectionString'\n          value: applicationInsights.properties.ConnectionString\n        }\n      ]\n    }\n  }\n}\n\nresource applicationInsights 'Microsoft.Insights\/components@2020-02-02' = {\n  name: 'ai-myapp-${environmentName}'\n  location: location\n  kind: 'web'\n  properties: {\n    Application_Type: 'web'\n    Request_Source: 'rest'\n    RetentionInDays: 90\n    WorkspaceResourceId: logAnalyticsWorkspace.id\n  }\n}\n\nresource logAnalyticsWorkspace 'Microsoft.OperationalInsights\/workspaces@2022-10-01' = {\n  name: 'log-myapp-${environmentName}'\n  location: location\n  properties: {\n    sku: {\n      name: 'PerGB2018'\n    }\n    retentionInDays: 30\n  }\n}\n\nresource keyVault 'Microsoft.KeyVault\/vaults@2022-07-01' = {\n  name: 'kv-myapp-${environmentName}-${uniqueString(resourceGroup().id)}'\n  location: location\n  properties: {\n    sku: {\n      family: 'A'\n      name: 'standard'\n    }\n    tenantId: subscription().tenantId\n    accessPolicies: [\n      {\n        tenantId: subscription().tenantId\n        objectId: webApp.identity.principalId\n        permissions: {\n          secrets: [\n            'get'\n            'list'\n          ]\n        }\n      }\n    ]\n    enableRbacAuthorization: false\n    enableSoftDelete: true\n    softDeleteRetentionInDays: 7\n  }\n}\n\noutput webAppName string = webApp.name\noutput webAppUrl string = 'https:\/\/${webApp.properties.defaultHostName}'\noutput keyVaultName string = keyVault.name\noutput applicationInsightsKey string = applicationInsights.properties.InstrumentationKey<\/pre>\n<h3>Deploy Infrastructure<\/h3>\n<p>Create infrastructure deployment pipeline (<code style=\"background-color: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', Consolas, monospace; font-size: 14px;\">infrastructure\/azure-pipelines.yml<\/code>):<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">trigger: none\n\nvariables:\n  azureSubscription: 'MyAzureSubscription'\n  resourceGroupPrefix: 'rg-myapp'\n  location: 'East US'\n\nstages:\n- stage: DeployInfrastructure\n  displayName: 'Deploy Infrastructure'\n  jobs:\n  - job: DeployDev\n    displayName: 'Deploy Development Infrastructure'\n    pool:\n      vmImage: 'ubuntu-latest'\n    steps:\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: 'Deploy Development Resources'\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: '$(azureSubscription)'\n        subscriptionId: '$(subscriptionId)'\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resourceGroupPrefix)-dev'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: 'infrastructure\/main.bicep'\n        overrideParameters: |\n          -environmentName \"dev\"\n          -appServicePlanSku \"F1\"\n        deploymentMode: 'Incremental'\n\n  - job: DeployStaging\n    displayName: 'Deploy Staging Infrastructure'\n    pool:\n      vmImage: 'ubuntu-latest'\n    steps:\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: 'Deploy Staging Resources'\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: '$(azureSubscription)'\n        subscriptionId: '$(subscriptionId)'\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resourceGroupPrefix)-staging'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: 'infrastructure\/main.bicep'\n        overrideParameters: |\n          -environmentName \"staging\"\n          -appServicePlanSku \"S1\"\n        deploymentMode: 'Incremental'\n\n  - job: DeployProduction\n    displayName: 'Deploy Production Infrastructure'\n    pool:\n      vmImage: 'ubuntu-latest'\n    steps:\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: 'Deploy Production Resources'\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: '$(azureSubscription)'\n        subscriptionId: '$(subscriptionId)'\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resourceGroupPrefix)-prod'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: 'infrastructure\/main.bicep'\n        overrideParameters: |\n          -environmentName \"prod\"\n          -appServicePlanSku \"P1V2\"\n        deploymentMode: 'Incremental'<\/pre>\n<h3>Option B: Terraform (Recommended for multi-cloud or Terraform-experienced teams)<\/h3>\n<p>Alternatively, you can use Terraform to manage the same infrastructure. Here&#8217;s the equivalent Terraform configuration:<\/p>\n<p><strong>main.tf:<\/strong><\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">terraform {\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt;3.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\n# Resource Group\nresource \"azurerm_resource_group\" \"main\" {\n  name     = \"rg-myapp-${var.environment_name}\"\n  location = var.location\n}\n\n# App Service Plan\nresource \"azurerm_service_plan\" \"main\" {\n  name                = \"asp-myapp-${var.environment_name}\"\n  resource_group_name = azurerm_resource_group.main.name\n  location           = azurerm_resource_group.main.location\n  os_type            = \"Windows\"\n  sku_name           = var.app_service_plan_sku\n}\n\n# Main Web App (Production Slot)\nresource \"azurerm_windows_web_app\" \"main\" {\n  name                = \"app-myapp-${var.environment_name}\"\n  resource_group_name = azurerm_resource_group.main.name\n  location           = azurerm_service_plan.main.location\n  service_plan_id    = azurerm_service_plan.main.id\n\n  site_config {\n    always_on = true\n    \n    application_stack {\n      dotnet_framework_version = \"v6.0\"\n    }\n  }\n\n  app_settings = {\n    \"ASPNETCORE_ENVIRONMENT\" = title(var.environment_name)\n    \"ApplicationInsights__ConnectionString\" = azurerm_application_insights.main.connection_string\n  }\n\n  identity {\n    type = \"SystemAssigned\"\n  }\n\n  https_only = true\n}\n\n# Staging Deployment Slot (only for production environment)\nresource \"azurerm_windows_web_app_slot\" \"staging\" {\n  count           = var.environment_name == \"prod\" ? 1 : 0\n  name            = \"staging\"\n  app_service_id  = azurerm_windows_web_app.main.id\n\n  site_config {\n    always_on = true\n    \n    application_stack {\n      dotnet_framework_version = \"v6.0\"\n    }\n  }\n\n  app_settings = {\n    \"ASPNETCORE_ENVIRONMENT\" = \"Staging\"\n    \"ApplicationInsights__ConnectionString\" = azurerm_application_insights.main.connection_string\n  }\n\n  identity {\n    type = \"SystemAssigned\"\n  }\n\n  https_only = true\n}\n\n# Application Insights\nresource \"azurerm_application_insights\" \"main\" {\n  name                = \"ai-myapp-${var.environment_name}\"\n  location           = azurerm_resource_group.main.location\n  resource_group_name = azurerm_resource_group.main.name\n  application_type   = \"web\"\n  retention_in_days  = 90\n  workspace_id       = azurerm_log_analytics_workspace.main.id\n}\n\n# Log Analytics Workspace\nresource \"azurerm_log_analytics_workspace\" \"main\" {\n  name                = \"log-myapp-${var.environment_name}\"\n  location           = azurerm_resource_group.main.location\n  resource_group_name = azurerm_resource_group.main.name\n  sku                = \"PerGB2018\"\n  retention_in_days  = 30\n}\n\n# Key Vault for secrets\nresource \"azurerm_key_vault\" \"main\" {\n  name                = \"kv-myapp-${var.environment_name}-${random_string.suffix.result}\"\n  location           = azurerm_resource_group.main.location\n  resource_group_name = azurerm_resource_group.main.name\n  tenant_id          = data.azurerm_client_config.current.tenant_id\n  sku_name           = \"standard\"\n\n  # Grant access to the web app's managed identity\n  access_policy {\n    tenant_id = data.azurerm_client_config.current.tenant_id\n    object_id = azurerm_windows_web_app.main.identity[0].principal_id\n\n    secret_permissions = [\n      \"Get\",\n      \"List\",\n    ]\n  }\n\n  # Grant access to staging slot if it exists\n  dynamic \"access_policy\" {\n    for_each = var.environment_name == \"prod\" ? [1] : []\n    content {\n      tenant_id = data.azurerm_client_config.current.tenant_id\n      object_id = azurerm_windows_web_app_slot.staging[0].identity[0].principal_id\n\n      secret_permissions = [\n        \"Get\",\n        \"List\",\n      ]\n    }\n  }\n}\n\nresource \"random_string\" \"suffix\" {\n  length  = 8\n  special = false\n  upper   = false\n}\n\ndata \"azurerm_client_config\" \"current\" {}\n<\/pre>\n<p><strong>variables.tf:<\/strong><\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">variable \"environment_name\" {\n  description = \"Environment name\"\n  type        = string\n  validation {\n    condition     = contains([\"dev\", \"staging\", \"prod\"], var.environment_name)\n    error_message = \"Environment must be dev, staging, or prod.\"\n  }\n}\n\nvariable \"location\" {\n  description = \"Azure region\"\n  type        = string\n  default     = \"East US\"\n}\n\nvariable \"app_service_plan_sku\" {\n  description = \"App Service Plan SKU\"\n  type        = string\n  default     = \"S1\"\n}\n<\/pre>\n<p><strong>terraform.tfvars (for different environments):<\/strong><\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\"># terraform.tfvars.prod\nenvironment_name = \"prod\"\nlocation = \"East US\"\napp_service_plan_sku = \"P1V2\"  # Production tier supports deployment slots\n\n# terraform.tfvars.staging  \nenvironment_name = \"staging\"\nlocation = \"East US\"\napp_service_plan_sku = \"S1\"  # No slots needed for staging environment\n\n# terraform.tfvars.dev\nenvironment_name = \"dev\"\nlocation = \"East US\"\napp_service_plan_sku = \"F1\"  # Free tier, no slots available\n<\/pre>\n<p><strong>Deploy with Terraform:<\/strong><\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\"># Initialize Terraform\nterraform init\n\n# Plan deployment\nterraform plan -var-file=\"terraform.tfvars.prod\"\n\n# Apply infrastructure\nterraform apply -var-file=\"terraform.tfvars.prod\" -auto-approve\n<\/pre>\n<h3>ARM vs Terraform: Which Should You Choose?<\/h3>\n<div style=\"background-color: #f8f9fa; padding: 15px; border-radius: 6px; margin: 20px 0; border: 1px solid #dee2e6;\">\n<p><strong>Choose ARM Templates\/Bicep if:<\/strong><\/p>\n<ul>\n<li>You&#8217;re working in a pure Azure environment<\/li>\n<li>Your team is Azure-focused<\/li>\n<li>You want native Azure tooling integration<\/li>\n<li>You need immediate access to new Azure features<\/li>\n<\/ul>\n<p><strong>Choose Terraform if:<\/strong><\/p>\n<ul>\n<li>You have multi-cloud infrastructure<\/li>\n<li>Your team has Terraform expertise<\/li>\n<li>You want vendor-neutral infrastructure code<\/li>\n<li>You need to manage non-Azure resources (DNS, monitoring tools, etc.)<\/li>\n<\/ul>\n<\/div>\n<h3>Deploy Infrastructure<\/h3>\n<p>If using ARM\/Bicep, create infrastructure deployment pipeline (<code style=\"background-color: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', Consolas, monospace; font-size: 14px;\">infrastructure\/azure-pipelines.yml<\/code>):<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">trigger: none\n\nvariables:\n  azureSubscription: 'MyAzureSubscription'\n  resourceGroupPrefix: 'rg-myapp'\n  location: 'East US'\n\nstages:\n- stage: DeployInfrastructure\n  displayName: 'Deploy Infrastructure'\n  jobs:\n  - job: DeployDev\n    displayName: 'Deploy Development Infrastructure'\n    pool:\n      vmImage: 'ubuntu-latest'\n    steps:\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: 'Deploy Development Resources'\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: '$(azureSubscription)'\n        subscriptionId: '$(subscriptionId)'\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resourceGroupPrefix)-dev'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: 'infrastructure\/main.bicep'\n        overrideParameters: |\n          -environmentName \"dev\"\n          -appServicePlanSku \"F1\"\n        deploymentMode: 'Incremental'\n\n  - job: DeployStaging\n    displayName: 'Deploy Staging Infrastructure'\n    pool:\n      vmImage: 'ubuntu-latest'\n    steps:\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: 'Deploy Staging Resources'\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: '$(azureSubscription)'\n        subscriptionId: '$(subscriptionId)'\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resourceGroupPrefix)-staging'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: 'infrastructure\/main.bicep'\n        overrideParameters: |\n          -environmentName \"staging\"\n          -appServicePlanSku \"S1\"\n        deploymentMode: 'Incremental'\n\n  - job: DeployProduction\n    displayName: 'Deploy Production Infrastructure'\n    pool:\n      vmImage: 'ubuntu-latest'\n    steps:\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: 'Deploy Production Resources'\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: '$(azureSubscription)'\n        subscriptionId: '$(subscriptionId)'\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resourceGroupPrefix)-prod'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: 'infrastructure\/main.bicep'\n        overrideParameters: |\n          -environmentName \"prod\"\n          -appServicePlanSku \"P1V2\"<\/pre>\n<h3>Application Settings<\/h3>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">\n\n    \nCreate environment-specific configuration files:\n\n    \n<strong>appsettings.Development.json:<\/strong>\n\n<\/pre>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"@Microsoft.KeyVault(SecretUri=https:\/\/kv-myapp-dev.vault.azure.net\/secrets\/DatabaseConnectionString\/)\"\n  },\n  \"ApplicationInsights\": {\n    \"ConnectionString\": \"\"\n  }\n}<\/pre>\n<p><strong>appsettings.Staging.json:<\/strong><\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"@Microsoft.KeyVault(SecretUri=https:\/\/kv-myapp-staging.vault.azure.net\/secrets\/DatabaseConnectionString\/)\"\n  },\n  \"ApplicationInsights\": {\n    \"ConnectionString\": \"\"\n  }\n}<\/pre>\n<p><strong>appsettings.Production.json:<\/strong><\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"@Microsoft.KeyVault(SecretUri=https:\/\/kv-myapp-prod.vault.azure.net\/secrets\/DatabaseConnectionString\/)\"\n  },\n  \"ApplicationInsights\": {\n    \"ConnectionString\": \"\"\n  }\n}<\/pre>\n<h3>Health Check Configuration<\/h3>\n<p>Add health checks to your application (<code style=\"background-color: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', Consolas, monospace; font-size: 14px;\">Program.cs<\/code>):<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">var builder = WebApplication.CreateBuilder(args);\n\n\/\/ Add services\nbuilder.Services.AddControllers();\nbuilder.Services.AddApplicationInsightsTelemetry();\nbuilder.Services.AddHealthChecks()\n    .AddCheck(\"self\", () =&gt; HealthCheckResult.Healthy())\n    .AddSqlServer(\n        builder.Configuration.GetConnectionString(\"DefaultConnection\"),\n        name: \"database\",\n        tags: new[] { \"db\", \"sql\", \"sqlserver\" });\n\nvar app = builder.Build();\n\n\/\/ Configure pipeline\nif (!app.Environment.IsDevelopment())\n{\n    app.UseExceptionHandler(\"\/Error\");\n    app.UseHsts();\n}\n\napp.UseHttpsRedirection();\napp.UseStaticFiles();\napp.UseRouting();\napp.UseAuthorization();\n\napp.MapControllers();\napp.MapHealthChecks(\"\/health\", new HealthCheckOptions\n{\n    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse\n});\n\napp.MapHealthChecks(\"\/health\/ready\", new HealthCheckOptions\n{\n    Predicate = check =&gt; check.Tags.Contains(\"ready\"),\n    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse\n});\n\napp.MapHealthChecks(\"\/health\/live\", new HealthCheckOptions\n{\n    Predicate = _ =&gt; false,\n    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse\n});\n\napp.Run();<\/pre>\n<h2>Step 3: Build Pipeline Configuration<\/h2>\n<p>Create the main build pipeline (<code style=\"background-color: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', Consolas, monospace; font-size: 14px;\">azure-pipelines.yml<\/code>):<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">trigger:\n  branches:\n    include:\n    - main\n    - develop\n  paths:\n    exclude:\n    - infrastructure\/*\n    - docs\/*\n    - README.md\n\nvariables:\n  buildConfiguration: 'Release'\n  dotNetFramework: 'net6.0'\n  dotNetVersion: '6.0.x'\n  buildPlatform: 'Any CPU'\n\npool:\n  vmImage: 'windows-latest'\n\nstages:\n- stage: Build\n  displayName: 'Build and Test'\n  jobs:\n  - job: BuildJob\n    displayName: 'Build Job'\n    steps:\n    - task: UseDotNet@2\n      displayName: 'Use .NET Core SDK $(dotNetVersion)'\n      inputs:\n        packageType: 'sdk'\n        version: '$(dotNetVersion)'\n\n    - task: DotNetCoreCLI@2\n      displayName: 'Restore NuGet packages'\n      inputs:\n        command: 'restore'\n        projects: '**\/*.csproj'\n        feedsToUse: 'select'\n\n    - task: DotNetCoreCLI@2\n      displayName: 'Build application'\n      inputs:\n        command: 'build'\n        projects: '**\/*.csproj'\n        arguments: '--configuration $(buildConfiguration) --no-restore'\n\n    - task: DotNetCoreCLI@2\n      displayName: 'Run unit tests'\n      inputs:\n        command: 'test'\n        projects: '**\/*Tests.csproj'\n        arguments: '--configuration $(buildConfiguration) --no-build --collect:\"XPlat Code Coverage\" --logger trx --results-directory $(Common.TestResultsDirectory)'\n        publishTestResults: true\n\n    - task: PublishCodeCoverageResults@1\n      displayName: 'Publish code coverage'\n      inputs:\n        codeCoverageTool: 'Cobertura'\n        summaryFileLocation: '$(Common.TestResultsDirectory)\/**\/*.cobertura.xml'\n\n    - task: DotNetCoreCLI@2\n      displayName: 'Publish application'\n      inputs:\n        command: 'publish'\n        projects: '**\/*.csproj'\n        arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)\/app --no-build'\n        publishWebProjects: true\n        zipAfterPublish: true\n\n    - task: PublishBuildArtifacts@1\n      displayName: 'Publish build artifacts'\n      inputs:\n        pathToPublish: '$(Build.ArtifactStagingDirectory)'\n        artifactName: 'drop'\n        publishLocation: 'Container'\n\n- stage: DeployDev\n  displayName: 'Deploy to Development'\n  dependsOn: Build\n  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs\/heads\/develop'))\n  variables:\n    environment: 'dev'\n    resourceGroup: 'rg-myapp-dev'\n    webAppName: 'app-myapp-dev'\n  jobs:\n  - deployment: DeployDev\n    displayName: 'Deploy to Development'\n    environment: 'Development'\n    strategy:\n      runOnce:\n        deploy:\n          steps:\n          - template: templates\/deploy-steps.yml\n            parameters:\n              environment: '$(environment)'\n              resourceGroup: '$(resourceGroup)'\n              webAppName: '$(webAppName)'\n              useSlots: false\n\n- stage: DeployStaging\n  displayName: 'Deploy to Staging'\n  dependsOn: Build\n  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs\/heads\/main'))\n  variables:\n    environment: 'staging'\n    resourceGroup: 'rg-myapp-staging'\n    webAppName: 'app-myapp-staging'\n  jobs:\n  - deployment: DeployStaging\n    displayName: 'Deploy to Staging'\n    environment: 'Staging'\n    strategy:\n      runOnce:\n        deploy:\n          steps:\n          - template: templates\/deploy-steps.yml\n            parameters:\n              environment: '$(environment)'\n              resourceGroup: '$(resourceGroup)'\n              webAppName: '$(webAppName)'\n              useSlots: false\n\n  - job: StagingTests\n    displayName: 'Run Staging Tests'\n    dependsOn: DeployStaging\n    pool:\n      vmImage: 'windows-latest'\n    steps:\n    - task: DotNetCoreCLI@2\n      displayName: 'Run integration tests'\n      inputs:\n        command: 'test'\n        projects: '**\/*IntegrationTests.csproj'\n        arguments: '--configuration $(buildConfiguration) --logger trx --results-directory $(Common.TestResultsDirectory)'\n        publishTestResults: true\n      env:\n        TEST_BASE_URL: 'https:\/\/app-myapp-staging.azurewebsites.net'\n\n- stage: SecurityScan\n  displayName: 'Security Scanning'\n  dependsOn: DeployStaging\n  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs\/heads\/main'))\n  jobs:\n  - job: SecurityScan\n    displayName: 'Security Scan'\n    pool:\n      vmImage: 'windows-latest'\n    steps:\n    - task: whitesource.ws-bolt.bolt.wss.WhiteSource Bolt@20\n      displayName: 'WhiteSource Bolt'\n      inputs:\n        cwd: '$(System.DefaultWorkingDirectory)'\n\n    - task: SonarCloudPrepare@1\n      displayName: 'Prepare SonarCloud analysis'\n      inputs:\n        SonarCloud: 'SonarCloud'\n        organization: 'your-organization'\n        scannerMode: 'MSBuild'\n        projectKey: 'myapp'\n        projectName: 'MyApp'\n        projectVersion: '$(Build.BuildNumber)'\n\n    - task: DotNetCoreCLI@2\n      displayName: 'Build for SonarCloud'\n      inputs:\n        command: 'build'\n        projects: '**\/*.csproj'\n        arguments: '--configuration $(buildConfiguration)'\n\n    - task: SonarCloudAnalyze@1\n      displayName: 'Run SonarCloud analysis'\n\n    - task: SonarCloudPublish@1\n      displayName: 'Publish SonarCloud results'\n      inputs:\n        pollingTimeoutSec: '300'\n\n- stage: ProductionApproval\n  displayName: 'Production Approval'\n  dependsOn: \n  - DeployStaging\n  - SecurityScan\n  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs\/heads\/main'))\n  jobs:\n  - job: waitForValidation\n    displayName: 'Wait for external validation'\n    pool: server\n    timeoutInMinutes: 4320 # 3 days\n    steps:\n    - task: ManualValidation@0\n      displayName: 'Manual validation'\n      inputs:\n        notifyUsers: |\n          admin@company.com\n          devops@company.com\n        instructions: 'Please validate the staging deployment and approve for production'\n        onTimeout: 'reject'\n\n- stage: DeployProduction\n  displayName: 'Deploy to Production'\n  dependsOn: ProductionApproval\n  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs\/heads\/main'))\n  variables:\n    environment: 'prod'\n    resourceGroup: 'rg-myapp-prod'\n    webAppName: 'app-myapp-prod'\n  jobs:\n  - deployment: DeployProduction\n    displayName: 'Deploy to Production'\n    environment: 'Production'\n    strategy:\n      runOnce:\n        deploy:\n          steps:\n          - template: templates\/deploy-steps.yml\n            parameters:\n              environment: '$(environment)'\n              resourceGroup: '$(resourceGroup)'\n              webAppName: '$(webAppName)'\n              useSlots: true<\/pre>\n<h2>Step 4: Deployment Templates<\/h2>\n<p>Create reusable deployment templates (<code style=\"background-color: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', Consolas, monospace; font-size: 14px;\">templates\/deploy-steps.yml<\/code>):<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">parameters:\n- name: environment\n  type: string\n- name: resourceGroup\n  type: string\n- name: webAppName\n  type: string\n- name: useSlots\n  type: boolean\n  default: false\n\nsteps:\n- download: current\n  artifact: drop\n  displayName: 'Download build artifacts'\n\n- task: AzureKeyVault@2\n  displayName: 'Get secrets from Key Vault'\n  inputs:\n    azureSubscription: 'MyAzureSubscription'\n    KeyVaultName: 'kv-myapp-${{ parameters.environment }}'\n    SecretsFilter: '*'\n    RunAsPreJob: false\n\n- ${{ if eq(parameters.useSlots, true) }}:\n  - task: AzureRmWebAppDeployment@4\n    displayName: 'Deploy to staging slot'\n    inputs:\n      ConnectionType: 'AzureRM'\n      azureSubscription: 'MyAzureSubscription'\n      appType: 'webApp'\n      WebAppName: '${{ parameters.webAppName }}'\n      deployToSlotOrASE: true\n      ResourceGroupName: '${{ parameters.resourceGroup }}'\n      SlotName: 'staging'\n      packageForLinux: '$(Pipeline.Workspace)\/drop\/app\/*.zip'\n      AppSettings: |\n        -ASPNETCORE_ENVIRONMENT \"${{ parameters.environment }}\"\n        -ApplicationInsights__ConnectionString \"$(ApplicationInsights--ConnectionString)\"\n        -ConnectionStrings__DefaultConnection \"$(DatabaseConnectionString)\"\n\n  - task: AzureAppServiceManage@0\n    displayName: 'Start staging slot'\n    inputs:\n      azureSubscription: 'MyAzureSubscription'\n      Action: 'Start Azure App Service'\n      WebAppName: '${{ parameters.webAppName }}'\n      SpecifySlotOrASE: true\n      ResourceGroupName: '${{ parameters.resourceGroup }}'\n      Slot: 'staging'\n\n  - task: PowerShell@2\n    displayName: 'Validate staging slot'\n    inputs:\n      targetType: 'inline'\n      script: |\n        $url = \"https:\/\/${{ parameters.webAppName }}-staging.azurewebsites.net\/health\"\n        Write-Host \"Testing health endpoint: $url\"\n        \n        $maxAttempts = 10\n        $attempt = 0\n        $success = $false\n        \n        while ($attempt -lt $maxAttempts -and -not $success) {\n            try {\n                $response = Invoke-RestMethod -Uri $url -Method Get -TimeoutSec 30\n                if ($response) {\n                    Write-Host \"Health check passed!\"\n                    $success = $true\n                } else {\n                    Write-Host \"Health check failed. Attempt $($attempt + 1) of $maxAttempts\"\n                }\n            } catch {\n                Write-Host \"Error calling health endpoint: $($_.Exception.Message)\"\n            }\n            \n            if (-not $success) {\n                Start-Sleep -Seconds 30\n                $attempt++\n            }\n        }\n        \n        if (-not $success) {\n            Write-Error \"Health check failed after $maxAttempts attempts\"\n            exit 1\n        }\n\n  - task: AzureAppServiceManage@0\n    displayName: 'Swap staging to production'\n    inputs:\n      azureSubscription: 'MyAzureSubscription'\n      Action: 'Swap Slots'\n      WebAppName: '${{ parameters.webAppName }}'\n      ResourceGroupName: '${{ parameters.resourceGroup }}'\n      SourceSlot: 'staging'\n      SwapWithProduction: true\n\n- ${{ if eq(parameters.useSlots, false) }}:\n  - task: AzureRmWebAppDeployment@4\n    displayName: 'Deploy to App Service'\n    inputs:\n      ConnectionType: 'AzureRM'\n      azureSubscription: 'MyAzureSubscription'\n      appType: 'webApp'\n      WebAppName: '${{ parameters.webAppName }}'\n      ResourceGroupName: '${{ parameters.resourceGroup }}'\n      packageForLinux: '$(Pipeline.Workspace)\/drop\/app\/*.zip'\n      AppSettings: |\n        -ASPNETCORE_ENVIRONMENT \"${{ parameters.environment }}\"\n        -ApplicationInsights__ConnectionString \"$(ApplicationInsights--ConnectionString)\"\n        -ConnectionStrings__DefaultConnection \"$(DatabaseConnectionString)\"\n\n- task: PowerShell@2\n  displayName: 'Post-deployment validation'\n  inputs:\n    targetType: 'inline'\n    script: |\n      $url = \"https:\/\/${{ parameters.webAppName }}.azurewebsites.net\/health\"\n      Write-Host \"Testing production health endpoint: $url\"\n      \n      $maxAttempts = 5\n      $attempt = 0\n      $success = $false\n      \n      while ($attempt -lt $maxAttempts -and -not $success) {\n          try {\n              $response = Invoke-RestMethod -Uri $url -Method Get -TimeoutSec 30\n              if ($response) {\n                  Write-Host \"Production health check passed!\"\n                  $success = $true\n              }\n          } catch {\n              Write-Host \"Error calling production health endpoint: $($_.Exception.Message)\"\n          }\n          \n          if (-not $success) {\n              Start-Sleep -Seconds 15\n              $attempt++\n          }\n      }\n      \n      if (-not $success) {\n          Write-Error \"Production health check failed after $maxAttempts attempts\"\n          exit 1\n      }\n\n- task: AzureCLI@2\n  displayName: 'Configure monitoring alerts'\n  inputs:\n    azureSubscription: 'MyAzureSubscription'\n    scriptType: 'ps'\n    scriptLocation: 'inlineScript'\n    inlineScript: |\n      # Create action group for alerts\n      az monitor action-group create `\n        --name \"myapp-alerts\" `\n        --resource-group \"${{ parameters.resourceGroup }}\" `\n        --short-name \"MyAppAlert\" `\n        --email-receivers name=\"DevOps Team\" email=\"devops@company.com\"\n      \n      # Create availability alert\n      az monitor metrics alert create `\n        --name \"myapp-availability-alert\" `\n        --resource-group \"${{ parameters.resourceGroup }}\" `\n        --scopes \"\/subscriptions\/$(az account show --query id -o tsv)\/resourceGroups\/${{ parameters.resourceGroup }}\/providers\/Microsoft.Web\/sites\/${{ parameters.webAppName }}\" `\n        --condition \"avg Availability &lt; 99\" `\n        --description \"Alert when availability drops below 99%\" `\n        --evaluation-frequency 1m `\n        --window-size 5m `\n        --severity 2 `\n        --action-groups \"\/subscriptions\/$(az account show --query id -o tsv)\/resourceGroups\/${{ parameters.resourceGroup }}\/providers\/microsoft.insights\/actionGroups\/myapp-alerts\"<\/pre>\n<h2>Step 5: Variable Groups and Environments<\/h2>\n<h3>Create Variable Groups<\/h3>\n<p>In Azure DevOps, create variable groups for each environment:<\/p>\n<p><strong>Development Variables:<\/strong><\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">Environment: Development\nDatabaseConnectionString: (linked to Key Vault)\nApplicationInsights.ConnectionString: (from deployment output)<\/pre>\n<p><strong>Staging Variables:<\/strong><\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">Environment: Staging\nDatabaseConnectionString: (linked to Key Vault)\nApplicationInsights.ConnectionString: (from deployment output)<\/pre>\n<p><strong>Production Variables:<\/strong><\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">Environment: Production\nDatabaseConnectionString: (linked to Key Vault)\nApplicationInsights.ConnectionString: (from deployment output)<\/pre>\n<h3>Configure Environments<\/h3>\n<p>Create environments in Azure DevOps with appropriate approvals and checks:<\/p>\n<ol>\n<li><strong>Development<\/strong>: Auto-approval<\/li>\n<li><strong>Staging<\/strong>: Auto-approval with branch protection (main only)<\/li>\n<li><strong>Production<\/strong>: Manual approval required with 2-person approval policy<\/li>\n<\/ol>\n<h2>Step 6: Advanced Production Features<\/h2>\n<h3>Blue\/Green Deployment with Traffic Splitting<\/h3>\n<p>Add traffic splitting configuration:<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">- task: AzureAppServiceManage@0\n  displayName: 'Configure traffic routing (10% to staging)'\n  inputs:\n    azureSubscription: 'MyAzureSubscription'\n    Action: 'Swap Slots'\n    WebAppName: '${{ parameters.webAppName }}'\n    ResourceGroupName: '${{ parameters.resourceGroup }}'\n    SourceSlot: 'staging'\n    SwapWithProduction: false\n    PreserveVnet: true\n    RouteTrafficPercentage: 10\n\n- task: PowerShell@2\n  displayName: 'Monitor metrics during canary deployment'\n  inputs:\n    targetType: 'inline'\n    script: |\n      # Monitor for 10 minutes\n      $endTime = (Get-Date).AddMinutes(10)\n      \n      while ((Get-Date) -lt $endTime) {\n          # Check error rate, response time, etc.\n          $errorRate = # Query Application Insights\n          \n          if ($errorRate -gt 0.05) {  # 5% error threshold\n              Write-Error \"High error rate detected: $errorRate\"\n              exit 1\n          }\n          \n          Start-Sleep -Seconds 60\n      }\n\n- task: AzureAppServiceManage@0\n  displayName: 'Complete swap to production'\n  inputs:\n    azureSubscription: 'MyAzureSubscription'\n    Action: 'Swap Slots'\n    WebAppName: '${{ parameters.webAppName }}'\n    ResourceGroupName: '${{ parameters.resourceGroup }}'\n    SourceSlot: 'staging'\n    SwapWithProduction: true<\/pre>\n<h3>Automated Rollback<\/h3>\n<p>Implement automated rollback capabilities:<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">- task: PowerShell@2\n  displayName: 'Monitor post-deployment metrics'\n  inputs:\n    targetType: 'inline'\n    script: |\n      $monitoringDuration = 300  # 5 minutes\n      $checkInterval = 30        # 30 seconds\n      $endTime = (Get-Date).AddSeconds($monitoringDuration)\n      \n      while ((Get-Date) -lt $endTime) {\n          try {\n              # Check health endpoint\n              $healthResponse = Invoke-RestMethod -Uri \"https:\/\/${{ parameters.webAppName }}.azurewebsites.net\/health\" -TimeoutSec 10\n              \n              # Check Application Insights metrics\n              $errorRate = # Query error rate from App Insights\n              $responseTime = # Query average response time\n              \n              if ($errorRate -gt 0.05 -or $responseTime -gt 2000) {\n                  Write-Error \"Performance degradation detected. Initiating rollback...\"\n                  \n                  # Trigger rollback\n                  az webapp deployment slot swap --name \"${{ parameters.webAppName }}\" --resource-group \"${{ parameters.resourceGroup }}\" --slot staging --target-slot production\n                  \n                  exit 1\n              }\n              \n              Write-Host \"Metrics within acceptable range. Error rate: $errorRate, Response time: $responseTime ms\"\n              \n          } catch {\n              Write-Warning \"Error checking metrics: $($_.Exception.Message)\"\n          }\n          \n          Start-Sleep -Seconds $checkInterval\n      }\n      \n      Write-Host \"Post-deployment monitoring completed successfully\"<\/pre>\n<h3>Database Migration Pipeline<\/h3>\n<p>Create a separate pipeline for database migrations:<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\"># database-migration-pipeline.yml\ntrigger: none\n\nparameters:\n- name: environment\n  displayName: Environment\n  type: string\n  default: staging\n  values:\n  - staging\n  - production\n\nvariables:\n  environment: ${{ parameters.environment }}\n\nstages:\n- stage: DatabaseMigration\n  displayName: 'Database Migration - $(environment)'\n  jobs:\n  - job: Migration\n    displayName: 'Run Database Migration'\n    pool:\n      vmImage: 'windows-latest'\n    steps:\n    - task: UseDotNet@2\n      inputs:\n        packageType: 'sdk'\n        version: '6.0.x'\n\n    - task: AzureKeyVault@2\n      inputs:\n        azureSubscription: 'MyAzureSubscription'\n        KeyVaultName: 'kv-myapp-$(environment)'\n        SecretsFilter: 'DatabaseConnectionString'\n\n    - task: DotNetCoreCLI@2\n      displayName: 'Run EF Migrations'\n      inputs:\n        command: 'custom'\n        custom: 'ef'\n        arguments: 'database update --connection \"$(DatabaseConnectionString)\" --project MyApp.Data --startup-project MyApp.Web'\n      env:\n        ConnectionStrings__DefaultConnection: '$(DatabaseConnectionString)'\n\n    - task: PowerShell@2\n      displayName: 'Verify Migration'\n      inputs:\n        targetType: 'inline'\n        script: |\n          # Run verification queries to ensure migration succeeded\n          # This could include checking table structure, data integrity, etc.<\/pre>\n<h2>Step 7: Monitoring and Observability<\/h2>\n<h3>Application Insights Integration<\/h3>\n<p>Configure detailed monitoring:<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">\/\/ In Program.cs\nbuilder.Services.AddApplicationInsightsTelemetry(options =&gt;\n{\n    options.ConnectionString = builder.Configuration[\"ApplicationInsights:ConnectionString\"];\n});\n\nbuilder.Services.AddSingleton&lt;ITelemetryInitializer, CustomTelemetryInitializer&gt;();\n\n\/\/ Custom telemetry initializer\npublic class CustomTelemetryInitializer : ITelemetryInitializer\n{\n    public void Initialize(ITelemetry telemetry)\n    {\n        if (telemetry is RequestTelemetry requestTelemetry)\n        {\n            requestTelemetry.Properties[\"Environment\"] = Environment.GetEnvironmentVariable(\"ASPNETCORE_ENVIRONMENT\");\n            requestTelemetry.Properties[\"Version\"] = Assembly.GetExecutingAssembly().GetName().Version?.ToString();\n        }\n    }\n}<\/pre>\n<h3>Dashboard Creation<\/h3>\n<p>Create Azure Dashboard for monitoring:<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">{\n  \"properties\": {\n    \"lenses\": [\n      {\n        \"order\": 0,\n        \"parts\": [\n          {\n            \"position\": { \"x\": 0, \"y\": 0, \"rowSpan\": 4, \"colSpan\": 6 },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"ComponentId\",\n                  \"value\": \"\/subscriptions\/{subscription-id}\/resourceGroups\/rg-myapp-prod\/providers\/microsoft.insights\/components\/ai-myapp-prod\"\n                }\n              ],\n              \"type\": \"Extension\/AppInsightsExtension\/PartType\/AvailabilityNavButtonPart\"\n            }\n          },\n          {\n            \"position\": { \"x\": 6, \"y\": 0, \"rowSpan\": 4, \"colSpan\": 6 },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"ComponentId\", \n                  \"value\": \"\/subscriptions\/{subscription-id}\/resourceGroups\/rg-myapp-prod\/providers\/microsoft.insights\/components\/ai-myapp-prod\"\n                }\n              ],\n              \"type\": \"Extension\/AppInsightsExtension\/PartType\/PerformanceNavButtonPart\"\n            }\n          }\n        ]\n      }\n    ]\n  }\n}<\/pre>\n<h2>Step 8: Security and Compliance<\/h2>\n<h3>Secure Configuration Management<\/h3>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">- task: AzureKeyVault@2\n  displayName: 'Get secrets from Key Vault'\n  inputs:\n    azureSubscription: 'MyAzureSubscription'\n    KeyVaultName: 'kv-myapp-$(environment)'\n    SecretsFilter: |\n      DatabaseConnectionString\n      ApiKey\n      JwtSecret\n    RunAsPreJob: true\n\n- task: FileTransform@1\n  displayName: 'Transform configuration files'\n  inputs:\n    folderPath: '$(Pipeline.Workspace)\/drop\/app'\n    fileType: 'json'\n    targetFiles: '**\/appsettings.json'<\/pre>\n<h3>Compliance Scanning<\/h3>\n<p>Add compliance checks to your pipeline:<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">- task: ms-codeanalysis.vss-microsoft-security-code-analysis-devops.build-task-credscan.CredScan@2\n  displayName: 'Run Credential Scanner'\n  inputs:\n    toolMajorVersion: 'V2'\n    scanFolder: '$(Build.SourcesDirectory)'\n    debugMode: false\n\n- task: ms-codeanalysis.vss-microsoft-security-code-analysis-devops.build-task-binskim.BinSkim@3\n  displayName: 'Run BinSkim'\n  inputs:\n    InputType: 'Basic'\n    Function: 'analyze'\n    AnalyzeTarget: '$(Build.ArtifactStagingDirectory)\/**\/*.dll;$(Build.ArtifactStagingDirectory)\/**\/*.exe'\n\n- task: ms-codeanalysis.vss-microsoft-security-code-analysis-devops.build-task-postanalysis.PostAnalysis@1\n  displayName: 'Post Analysis'\n  inputs:\n    AllTools: false\n    BinSkim: true\n    CredScan: true\n    ToolLogsNotFoundAction: 'Standard'<\/pre>\n<h2>Step 9: Performance Testing<\/h2>\n<p>Add performance testing stage:<\/p>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">- stage: PerformanceTesting\n  displayName: 'Performance Testing'\n  dependsOn: DeployStaging\n  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs\/heads\/main'))\n  jobs:\n  - job: LoadTest\n    displayName: 'Run Load Tests'\n    pool:\n      vmImage: 'ubuntu-latest'\n    steps:\n    - task: AzureLoadTest@1\n      displayName: 'Azure Load Testing'\n      inputs:\n        azureSubscription: 'MyAzureSubscription'\n        loadTestConfigFile: 'loadtest\/config.yaml'\n        loadTestResource: 'loadtest-myapp'\n        resourceGroup: 'rg-myapp-shared'\n        env: |\n          [\n            {\n              \"name\": \"webapp-url\",\n              \"value\": \"https:\/\/app-myapp-staging.azurewebsites.net\"\n            }\n          ]\n\n    - task: PublishTestResults@2\n      displayName: 'Publish load test results'\n      inputs:\n        testResultsFormat: 'JUnit'\n        testResultsFiles: '$(System.DefaultWorkingDirectory)\/**\/*loadtest-results.xml'\n        failTaskOnFailedTests: true<\/pre>\n<h2>Step 10: Disaster Recovery and Backup<\/h2>\n<h3>Automated Backup Configuration<\/h3>\n<pre style=\"background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 20px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.4;\">- task: AzureCLI@2\n  displayName: 'Configure backup policy'\n  inputs:\n    azureSubscription: 'MyAzureSubscription'\n    scriptType: 'bash'\n    scriptLocation: 'inlineScript'\n    inlineScript: |\n      # Create storage account for backups\n      az storage account create \\\n        --name \"stmyappbackup$(environment)\" \\\n        --resource-group \"$(resourceGroup)\" \\\n        --location \"East US\" \\\n        --sku \"Standard_LRS\"\n      \n      # Configure app service backup\n      az webapp config backup update \\\n        --resource-group \"$(resourceGroup)\" \\\n        --webapp-name \"$(webAppName)\" \\\n        --container-url \"$(az storage account show-connection-string --name stmyappbackup$(environment) --resource-group $(resourceGroup) --query connectionString -o tsv)\" \\\n        --frequency 24 \\\n        --retain-one true \\\n        --retention-period-in-days 30<\/pre>\n<h2>Conclusion<\/h2>\n<p>This comprehensive Azure DevOps pipeline provides enterprise-grade capabilities including:<\/p>\n<ul>\n<li><strong>Infrastructure as Code<\/strong> with Bicep templates<\/li>\n<li><strong>Multi-environment deployments<\/strong> with appropriate gates<\/li>\n<li><strong>Zero-downtime deployments<\/strong> using slot swaps<\/li>\n<li><strong>Automated testing<\/strong> at multiple stages<\/li>\n<li><strong>Security scanning<\/strong> and compliance checks<\/li>\n<li><strong>Performance testing<\/strong> integration<\/li>\n<li><strong>Monitoring and alerting<\/strong> setup<\/li>\n<li><strong>Automated rollback<\/strong> capabilities<\/li>\n<li><strong>Disaster recovery<\/strong> configurations<\/li>\n<\/ul>\n<p>The pipeline ensures high availability, security, and maintainability while providing the flexibility to adapt to changing requirements. Regular monitoring and continuous improvement of the pipeline based on operational feedback will help maintain its effectiveness in production environments.<\/p>\n<p>Key benefits of this approach include reduced deployment risk, faster time-to-market, improved application quality, and enhanced operational visibility across the entire deployment lifecycle.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Creating enterprise-grade release pipelines in Azure requires a comprehensive understanding of Azure DevOps services, proper configuration, and adherence to production best practices. This detailed guide will walk you through building a robust CI\/CD pipeline that deploys applications to Azure App Services with slot-based deployments for zero-downtime releases. Architecture Overview Our production pipeline will deploy a .NET web application to Azure<a href=\"https:\/\/nicktailor.com\/tech-blog\/building-production-ready-release-pipelines-in-azure-a-step-by-step-guide-using-arm-templates\/\" class=\"read-more\">Read More &#8230;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[146],"tags":[],"class_list":["post-2030","post","type-post","status-publish","format-standard","hentry","category-azure"],"_links":{"self":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts\/2030","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/comments?post=2030"}],"version-history":[{"count":5,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts\/2030\/revisions"}],"predecessor-version":[{"id":2036,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts\/2030\/revisions\/2036"}],"wp:attachment":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/media?parent=2030"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/categories?post=2030"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/tags?post=2030"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}