## 🚨 CRITICAL GUIDELINES ### Windows File Path Requirements **MANDATORY: Always Use Backslashes on Windows for File Paths** When using Edit or Write tools on Windows, you MUST use backslashes (`\`) in file paths, NOT forward slashes (`/`). **Examples:** - ❌ WRONG: `D:/repos/project/file.tsx` - ✅ CORRECT: `D:\repos\project\file.tsx` This applies to: - Edit tool file_path parameter - Write tool file_path parameter - All file operations on Windows systems ### Documentation Guidelines **NEVER create new documentation files unless explicitly requested by the user.** - **Priority**: Update existing README.md files rather than creating new documentation - **Repository cleanliness**: Keep repository root clean - only README.md unless user requests otherwise - **Style**: Documentation should be concise, direct, and professional - avoid AI-generated tone - **User preference**: Only create additional .md files when user specifically asks for documentation --- # Azure Deployment Stacks - 2025 GA Features Complete knowledge base for Azure Deployment Stacks, the successor to Azure Blueprints (GA 2024, best practices 2025). ## Overview Azure Deployment Stacks is a resource type for managing a collection of Azure resources as a single, atomic unit. It provides unified lifecycle management, resource protection, and automatic cleanup capabilities. ## Key Features ### 1. Unified Resource Management - Manage multiple resources as a single entity - Update, export, and delete operations on the entire stack - Track all managed resources in one place - Consistent deployment across environments ### 2. Deny Settings (Resource Protection) Prevent unauthorized modifications to managed resources: - **None**: No restrictions (default) - **DenyDelete**: Prevent resource deletion - **DenyWriteAndDelete**: Prevent updates and deletions ### 3. ActionOnUnmanage (Cleanup Policies) Control what happens to resources no longer in template: - **detachAll**: Remove from stack management, keep resources - **deleteAll**: Delete resources not in template - **deleteResources**: Delete unmanaged resources, keep resource groups ### 4. Scope Flexibility Deploy stacks at: - Resource group scope - Subscription scope - Management group scope ### 5. Replaces Azure Blueprints Azure Blueprints will be deprecated in **July 2026**. Deployment Stacks is the recommended replacement. ## Prerequisites ### Azure CLI Version ```bash # Requires Azure CLI 2.61.0 or later az version # Upgrade if needed az upgrade ``` ### Azure PowerShell Version ```bash # Requires Azure PowerShell 12.0.0 or later Get-InstalledModule -Name Az Update-Module -Name Az ``` ## Creating Deployment Stacks ### Subscription Scope Stack ```bash # Create deployment stack at subscription level az stack sub create \ --name MyProductionStack \ --location eastus \ --template-file main.bicep \ --parameters @parameters.json \ --deny-settings-mode DenyWriteAndDelete \ --deny-settings-excluded-principals \ --action-on-unmanage deleteAll \ --description "Production infrastructure managed by deployment stack" \ --tags Environment=Production ManagedBy=DeploymentStack CostCenter=Engineering # What-if analysis before deployment az stack sub what-if \ --name MyProductionStack \ --location eastus \ --template-file main.bicep \ --parameters @parameters.json # Create with confirmation prompt disabled az stack sub create \ --name MyDevStack \ --location eastus \ --template-file main.bicep \ --deny-settings-mode None \ --action-on-unmanage detachAll \ --yes ``` ### Resource Group Scope Stack ```bash # Create resource group az group create \ --name MyRG \ --location eastus \ --tags Environment=Production # Create deployment stack az stack group create \ --name MyAppStack \ --resource-group MyRG \ --template-file main.bicep \ --parameters environment=production \ --deny-settings-mode DenyDelete \ --action-on-unmanage deleteAll \ --description "Application infrastructure stack" ``` ### Management Group Scope Stack ```bash # Create stack at management group level az stack mg create \ --name MyEnterpriseStack \ --management-group-id MyMgmtGroup \ --location eastus \ --template-file main.bicep \ --deny-settings-mode DenyWriteAndDelete \ --action-on-unmanage detachAll ``` ## Bicep Template for Deployment Stack ### Production Stack Template ```bicep // main.bicep targetScope = 'subscription' @description('Environment name') @allowed([ 'dev' 'staging' 'production' ]) param environment string = 'production' @description('Primary location') param location string = 'eastus' @description('Secondary location for geo-replication') param secondaryLocation string = 'westus' // Resource naming var namingPrefix = 'myapp-${environment}' // Resource Group for core infrastructure resource coreRG 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: '${namingPrefix}-core-rg' location: location tags: { Environment: environment ManagedBy: 'DeploymentStack' Purpose: 'Core Infrastructure' } } // Resource Group for data services resource dataRG 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: '${namingPrefix}-data-rg' location: location tags: { Environment: environment ManagedBy: 'DeploymentStack' Purpose: 'Data Services' } } // Log Analytics Workspace module logAnalytics 'modules/log-analytics.bicep' = { name: 'logAnalyticsDeploy' scope: coreRG params: { name: '${namingPrefix}-logs' location: location retentionInDays: environment == 'production' ? 90 : 30 } } // AKS Automatic Cluster module aksCluster 'modules/aks-automatic.bicep' = { name: 'aksClusterDeploy' scope: coreRG params: { name: '${namingPrefix}-aks' location: location kubernetesVersion: '1.34' workspaceId: logAnalytics.outputs.workspaceId enableZoneRedundancy: environment == 'production' } } // Container Apps Environment module containerEnv 'modules/container-env.bicep' = { name: 'containerEnvDeploy' scope: coreRG params: { name: '${namingPrefix}-containerenv' location: location workspaceId: logAnalytics.outputs.workspaceId zoneRedundant: environment == 'production' } } // Azure OpenAI module openAI 'modules/openai.bicep' = { name: 'openAIDeploy' scope: dataRG params: { name: '${namingPrefix}-openai' location: location deployGPT5: environment == 'production' } } // Cosmos DB with geo-replication module cosmosDB 'modules/cosmos-db.bicep' = { name: 'cosmosDBDeploy' scope: dataRG params: { name: '${namingPrefix}-cosmos' primaryLocation: location secondaryLocation: secondaryLocation enableAutomaticFailover: environment == 'production' } } // Key Vault module keyVault 'modules/key-vault.bicep' = { name: 'keyVaultDeploy' scope: coreRG params: { name: '${namingPrefix}-kv' location: location enablePurgeProtection: environment == 'production' } } // Outputs output aksClusterName string = aksCluster.outputs.clusterName output containerEnvId string = containerEnv.outputs.environmentId output openAIEndpoint string = openAI.outputs.endpoint output cosmosDBEndpoint string = cosmosDB.outputs.endpoint output keyVaultUri string = keyVault.outputs.vaultUri ``` ### AKS Automatic Module ```bicep // modules/aks-automatic.bicep @description('Cluster name') param name string @description('Location') param location string @description('Kubernetes version') param kubernetesVersion string = '1.34' @description('Log Analytics workspace ID') param workspaceId string @description('Enable zone redundancy') param enableZoneRedundancy bool = true resource aksCluster 'Microsoft.ContainerService/managedClusters@2025-01-01' = { name: name location: location sku: { name: 'Automatic' tier: 'Standard' } identity: { type: 'SystemAssigned' } properties: { kubernetesVersion: kubernetesVersion dnsPrefix: '${name}-dns' enableRBAC: true aadProfile: { managed: true enableAzureRBAC: true } networkProfile: { networkPlugin: 'azure' networkPluginMode: 'overlay' networkDataplane: 'cilium' serviceCidr: '10.0.0.0/16' dnsServiceIP: '10.0.0.10' } autoScalerProfile: { 'balance-similar-node-groups': 'true' expander: 'least-waste' } autoUpgradeProfile: { upgradeChannel: 'stable' nodeOSUpgradeChannel: 'NodeImage' } securityProfile: { defender: { securityMonitoring: { enabled: true } } workloadIdentity: { enabled: true } } oidcIssuerProfile: { enabled: true } addonProfiles: { omsagent: { enabled: true config: { logAnalyticsWorkspaceResourceID: workspaceId } } azurePolicy: { enabled: true } } } zones: enableZoneRedundancy ? ['1', '2', '3'] : null } output clusterName string = aksCluster.name output clusterId string = aksCluster.id output oidcIssuerUrl string = aksCluster.properties.oidcIssuerProfile.issuerUrl output kubeletIdentity string = aksCluster.properties.identityProfile.kubeletidentity.objectId ``` ## Managing Deployment Stacks ### Update Stack ```bash # Update with new template version az stack sub update \ --name MyProductionStack \ --template-file main.bicep \ --parameters @parameters.json \ --action-on-unmanage deleteAll # Update deny settings az stack sub update \ --name MyProductionStack \ --deny-settings-mode DenyWriteAndDelete \ --deny-settings-excluded-principals ``` ### View Stack Details ```bash # Show stack information az stack sub show \ --name MyProductionStack \ --output json # List all stacks in subscription az stack sub list --output table # List stacks in resource group az stack group list \ --resource-group MyRG \ --output table ``` ### Export Stack Template ```bash # Export template from deployed stack az stack sub export \ --name MyProductionStack \ --output-file exported-stack.json # Export and save parameters az stack sub show \ --name MyProductionStack \ --query "parameters" \ --output json > parameters-backup.json ``` ### Delete Stack ```bash # Delete stack and all managed resources az stack sub delete \ --name MyProductionStack \ --action-on-unmanage deleteAll \ --yes # Delete stack but keep resources az stack sub delete \ --name MyProductionStack \ --action-on-unmanage detachAll \ --yes # Delete with confirmation prompt az stack sub delete --name MyProductionStack ``` ## Deny Settings in Detail ### DenyDelete Mode Prevents deletion but allows updates: ```bash az stack sub create \ --name MyStack \ --location eastus \ --template-file main.bicep \ --deny-settings-mode DenyDelete \ --deny-settings-excluded-principals \ \ ``` **Use cases:** - Protect production databases - Prevent accidental resource deletion - Allow configuration updates ### DenyWriteAndDelete Mode Prevents both updates and deletions: ```bash az stack sub create \ --name MyStack \ --location eastus \ --template-file main.bicep \ --deny-settings-mode DenyWriteAndDelete \ --deny-settings-excluded-principals ``` **Use cases:** - Immutable infrastructure - Compliance requirements - Critical production workloads ### Excluded Principals Bypass deny settings for specific identities: ```bash # Get principal IDs SERVICE_PRINCIPAL_ID=$(az ad sp show --id --query id -o tsv) ADMIN_GROUP_ID=$(az ad group show --group "Cloud Admins" --query id -o tsv) # Apply with exclusions az stack sub create \ --name MyStack \ --location eastus \ --template-file main.bicep \ --deny-settings-mode DenyWriteAndDelete \ --deny-settings-excluded-principals $SERVICE_PRINCIPAL_ID $ADMIN_GROUP_ID ``` ## ActionOnUnmanage Policies ### detachAll Resources are removed from stack management but not deleted: ```bash az stack sub create \ --name MyStack \ --location eastus \ --template-file main.bicep \ --action-on-unmanage detachAll ``` **Use when:** - Testing deployment changes - Migrating resources to another stack - Temporary stack management ### deleteAll All unmanaged resources are deleted: ```bash az stack sub create \ --name MyStack \ --location eastus \ --template-file main.bicep \ --action-on-unmanage deleteAll ``` **Use when:** - Ephemeral environments (dev, test) - Clean slate deployments - Strict infrastructure-as-code enforcement ### deleteResources Delete resources but keep resource groups: ```bash az stack sub create \ --name MyStack \ --location eastus \ --template-file main.bicep \ --action-on-unmanage deleteResources ``` ## RBAC for Deployment Stacks ### Built-in Roles **Azure Deployment Stack Contributor** - Manage deployment stacks - Cannot create or delete deny-assignments **Azure Deployment Stack Owner** - Full stack management - Can create and delete deny-assignments ### Assign Roles ```bash # Assign Stack Contributor role az role assignment create \ --assignee \ --role "Azure Deployment Stack Contributor" \ --scope /subscriptions/ # Assign Stack Owner role az role assignment create \ --assignee \ --role "Azure Deployment Stack Owner" \ --scope /subscriptions/ ``` ## CI/CD Integration ### GitHub Actions ```yaml name: Deploy Deployment Stack on: push: branches: [main] workflow_dispatch: permissions: id-token: write contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Azure Login uses: azure/login@v2 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: What-if Analysis run: | az stack sub what-if \ --name MyProductionStack \ --location eastus \ --template-file main.bicep \ --parameters @parameters.json - name: Deploy Stack run: | az stack sub create \ --name MyProductionStack \ --location eastus \ --template-file main.bicep \ --parameters @parameters.json \ --deny-settings-mode DenyWriteAndDelete \ --deny-settings-excluded-principals ${{ secrets.DEVOPS_PRINCIPAL_ID }} \ --action-on-unmanage deleteAll \ --yes ``` ### Azure DevOps Pipeline ```yaml trigger: branches: include: - main pool: vmImage: 'ubuntu-latest' variables: azureSubscription: 'MyAzureConnection' stackName: 'MyProductionStack' location: 'eastus' steps: - task: AzureCLI@2 displayName: 'What-if Analysis' inputs: azureSubscription: $(azureSubscription) scriptType: 'bash' scriptLocation: 'inlineScript' inlineScript: | az stack sub what-if \ --name $(stackName) \ --location $(location) \ --template-file main.bicep \ --parameters @parameters.json - task: AzureCLI@2 displayName: 'Deploy Stack' inputs: azureSubscription: $(azureSubscription) scriptType: 'bash' scriptLocation: 'inlineScript' inlineScript: | az stack sub create \ --name $(stackName) \ --location $(location) \ --template-file main.bicep \ --parameters @parameters.json \ --deny-settings-mode DenyWriteAndDelete \ --action-on-unmanage deleteAll \ --yes ``` ## Monitoring and Auditing ### View Stack Events ```bash # Get deployment operations az stack sub show \ --name MyProductionStack \ --query "deploymentId" \ --output tsv | \ xargs -I {} az deployment sub show --name {} # List managed resources az stack sub show \ --name MyProductionStack \ --query "resources[].id" \ --output table ``` ### Activity Logs ```bash # Query stack operations az monitor activity-log list \ --resource-group MyRG \ --namespace Microsoft.Resources \ --start-time 2025-01-01T00:00:00Z \ --query "[?contains(authorization.action, 'Microsoft.Resources/deploymentStacks')]" \ --output table ``` ## Migration from Azure Blueprints ### Assessment 1. **Inventory Blueprints**: List all blueprints and assignments 2. **Document Parameters**: Export parameters and configurations 3. **Plan Conversion**: Map blueprints to deployment stacks 4. **Test in Dev**: Validate converted templates ### Conversion Steps ```bash # 1. Export Blueprint as ARM template # (Use Azure Portal or PowerShell) # 2. Convert ARM to Bicep az bicep decompile --file blueprint-template.json # 3. Create Deployment Stack az stack sub create \ --name ConvertedFromBlueprint \ --location eastus \ --template-file converted.bicep \ --parameters @blueprint-parameters.json \ --deny-settings-mode DenyWriteAndDelete \ --action-on-unmanage detachAll # 4. Validate resources az stack sub show --name ConvertedFromBlueprint # 5. Delete Blueprint assignment (after validation) # Remove-AzBlueprintAssignment -Name MyBlueprintAssignment ``` ## Best Practices ✓ **Use Deployment Stacks for all new infrastructure** ✓ **Always run what-if analysis before deployment** ✓ **Use DenyWriteAndDelete for production stacks** ✓ **Exclude break-glass principals from deny settings** ✓ **Tag stacks with Environment, CostCenter, Owner** ✓ **Use deleteAll for ephemeral environments** ✓ **Use detachAll for migration scenarios** ✓ **Implement CI/CD pipelines for stack deployment** ✓ **Monitor stack operations via activity logs** ✓ **Document stack architecture and dependencies** ## Troubleshooting ### Stack Creation Fails ```bash # Check deployment errors az stack sub show \ --name MyStack \ --query "error" \ --output json # Validate template az deployment sub validate \ --location eastus \ --template-file main.bicep \ --parameters @parameters.json ``` ### Deny Settings Blocking Operations ```bash # Check deny assignments az role assignment list \ --scope /subscriptions/ \ --include-inherited \ --query "[?type=='Microsoft.Authorization/denyAssignments']" # Add principal to exclusions az stack sub update \ --name MyStack \ --deny-settings-excluded-principals ``` ### Resources Not Deleted ```bash # Check action-on-unmanage setting az stack sub show \ --name MyStack \ --query "actionOnUnmanage" \ --output tsv # Update to deleteAll az stack sub update \ --name MyStack \ --action-on-unmanage deleteAll ``` ## References - [Deployment Stacks Documentation](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-stacks) - [Deployment Stacks Quickstart](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/quickstart-create-deployment-stacks) - [Migrate from Blueprints](https://learn.microsoft.com/en-us/azure/governance/blueprints/how-to/migrate-to-deployment-stacks) Deployment Stacks represents the future of Azure infrastructure lifecycle management!