| name | deployment-stacks-2025 |
| description | Azure Deployment Stacks GA 2025 features for unified resource management, deny settings, and lifecycle management |
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
az version
az upgrade
Azure PowerShell Version
Get-InstalledModule -Name Az
Update-Module -Name Az
Creating Deployment Stacks
Subscription Scope Stack
az stack sub create \
--name MyProductionStack \
--location eastus \
--template-file main.bicep \
--parameters @parameters.json \
--deny-settings-mode DenyWriteAndDelete \
--deny-settings-excluded-principals <devops-service-principal-id> <admin-group-id> \
--action-on-unmanage deleteAll \
--description "Production infrastructure managed by deployment stack" \
--tags Environment=Production ManagedBy=DeploymentStack CostCenter=Engineering
az stack sub what-if \
--name MyProductionStack \
--location eastus \
--template-file main.bicep \
--parameters @parameters.json
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
az group create \
--name MyRG \
--location eastus \
--tags Environment=Production
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
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
// 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
// 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
az stack sub update \
--name MyProductionStack \
--template-file main.bicep \
--parameters @parameters.json \
--action-on-unmanage deleteAll
az stack sub update \
--name MyProductionStack \
--deny-settings-mode DenyWriteAndDelete \
--deny-settings-excluded-principals <new-principal-id>
View Stack Details
az stack sub show \
--name MyProductionStack \
--output json
az stack sub list --output table
az stack group list \
--resource-group MyRG \
--output table
Export Stack Template
az stack sub export \
--name MyProductionStack \
--output-file exported-stack.json
az stack sub show \
--name MyProductionStack \
--query "parameters" \
--output json > parameters-backup.json
Delete Stack
az stack sub delete \
--name MyProductionStack \
--action-on-unmanage deleteAll \
--yes
az stack sub delete \
--name MyProductionStack \
--action-on-unmanage detachAll \
--yes
az stack sub delete --name MyProductionStack
Deny Settings in Detail
DenyDelete Mode
Prevents deletion but allows updates:
az stack sub create \
--name MyStack \
--location eastus \
--template-file main.bicep \
--deny-settings-mode DenyDelete \
--deny-settings-excluded-principals \
<emergency-access-principal-id> \
<devops-service-principal-id>
Use cases:
- Protect production databases
- Prevent accidental resource deletion
- Allow configuration updates
DenyWriteAndDelete Mode
Prevents both updates and deletions:
az stack sub create \
--name MyStack \
--location eastus \
--template-file main.bicep \
--deny-settings-mode DenyWriteAndDelete \
--deny-settings-excluded-principals <break-glass-principal-id>
Use cases:
- Immutable infrastructure
- Compliance requirements
- Critical production workloads
Excluded Principals
Bypass deny settings for specific identities:
SERVICE_PRINCIPAL_ID=$(az ad sp show --id <app-id> --query id -o tsv)
ADMIN_GROUP_ID=$(az ad group show --group "Cloud Admins" --query id -o tsv)
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:
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:
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:
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
az role assignment create \
--assignee <user-or-service-principal-id> \
--role "Azure Deployment Stack Contributor" \
--scope /subscriptions/<subscription-id>
az role assignment create \
--assignee <admin-principal-id> \
--role "Azure Deployment Stack Owner" \
--scope /subscriptions/<subscription-id>
CI/CD Integration
GitHub Actions
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
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
az stack sub show \
--name MyProductionStack \
--query "deploymentId" \
--output tsv | \
xargs -I {} az deployment sub show --name {}
az stack sub show \
--name MyProductionStack \
--query "resources[].id" \
--output table
Activity Logs
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
- Inventory Blueprints: List all blueprints and assignments
- Document Parameters: Export parameters and configurations
- Plan Conversion: Map blueprints to deployment stacks
- Test in Dev: Validate converted templates
Conversion Steps
az bicep decompile --file blueprint-template.json
az stack sub create \
--name ConvertedFromBlueprint \
--location eastus \
--template-file converted.bicep \
--parameters @blueprint-parameters.json \
--deny-settings-mode DenyWriteAndDelete \
--action-on-unmanage detachAll
az stack sub show --name ConvertedFromBlueprint
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
az stack sub show \
--name MyStack \
--query "error" \
--output json
az deployment sub validate \
--location eastus \
--template-file main.bicep \
--parameters @parameters.json
Deny Settings Blocking Operations
az role assignment list \
--scope /subscriptions/<subscription-id> \
--include-inherited \
--query "[?type=='Microsoft.Authorization/denyAssignments']"
az stack sub update \
--name MyStack \
--deny-settings-excluded-principals <new-principal-id>
Resources Not Deleted
az stack sub show \
--name MyStack \
--query "actionOnUnmanage" \
--output tsv
az stack sub update \
--name MyStack \
--action-on-unmanage deleteAll
References
Deployment Stacks represents the future of Azure infrastructure lifecycle management!