Azure Deployment Stacks: Bicep's missing lifecycle layer
Plain Bicep deployments leave behind resources you remove from a template. Deployment Stacks fix that gap with automatic cleanup and drift protection — here is how they work in practice.
Remove a resource from a Bicep template and redeploy — what happens to the Azure resource? Nothing. It stays where it is, quietly accumulating cost and permissions, until someone manually tracks it down and deletes it. On a team, with dozens of environments, that someone often never materialises.
Deployment Stacks are Azure’s answer to this problem. Generally available since 2024, they wrap your Bicep (or ARM) template in a management layer that tracks everything that was deployed and gives you real control over what happens when something leaves the template. If you have been using Terraform and want equivalent lifecycle management in native Azure tooling, this is the feature you have been waiting for.
What a deployment stack actually is
A deployment stack is a Microsoft.Resources/deploymentStacks ARM resource. You deploy it the
same way as any other ARM resource — using the Azure CLI, Azure PowerShell, or the portal — but
instead of deploying resources directly, it wraps your Bicep file and deploys through it. The
stack then tracks every resource that file creates.
That tracking is the key insight: the stack maintains an explicit list of its managed resources. When you redeploy the stack with an updated template, anything no longer in the template becomes unmanaged, and you decide what happens to it.
The two controls that matter
Every stack creation or update command has two parameters you need to think about deliberately.
actionOnUnmanage — what happens to resources that fall out of the template:
detachAll— the resource stays in Azure, just no longer tracked by the stackdeleteResources— managed resources (but not resource groups) are deleteddeleteAll— both managed resources and managed resource groups are deleted
denySettings — protection against out-of-band changes while the stack is active:
none— no protection (identical to a plain deployment)denyDelete— managed resources cannot be deleted outside the stackdenyWriteAndDelete— no writes or deletes on managed resources, enforced via Azure deny assignments
The combination you choose depends on how strict you need to be. For a shared networking stack
managed by a platform team, I would typically pair deleteResources with denyDelete — clean
up what leaves the template, and protect what stays. For a sandbox environment meant to be wiped
after use, deleteAll on teardown makes cleanup a one-command operation.
Creating a stack
Creating a stack at the subscription scope, deploying resources across multiple resource groups:
az stack sub create \
--name 'platform-connectivity' \
--location 'westeurope' \
--template-file './main.bicep' \
--action-on-unmanage 'deleteResources' \
--deny-settings-mode 'denyDelete'
Or with Azure PowerShell:
New-AzSubscriptionDeploymentStack `
-Name 'platform-connectivity' `
-Location 'westeurope' `
-TemplateFile './main.bicep' `
-ActionOnUnmanage 'deleteResources' `
-DenySettingsMode 'denyDelete'
Stacks also work at resource group scope (az stack group create /
New-AzResourceGroupDeploymentStack) and management group scope for policy-layer resources.
Minimum versions required: Azure PowerShell 12.0.0 or Azure CLI 2.61.0.
The cleanup lifecycle in practice
When you remove a resource from your Bicep file and redeploy, the stack computes the diff between
what is now in the template and what it was previously tracking. Resources that fall out of that
set are handled according to your actionOnUnmanage setting — no manual hunting required.
This matters most for ephemeral workloads: developer sandboxes, test environments, project-scoped resources with a defined end date. Instead of relying on someone to remember to clean up, the stack teardown handles it:
az stack sub delete \
--name 'sandbox-dev' \
--action-on-unmanage 'deleteAll'
One command, and every resource the template ever deployed is gone. Compared to assembling a
deletion list manually — or worse, running az resource list and deleting in a loop hoping you
got everything — this is a different category of confidence.
Deny settings and drift protection
In platform team contexts, one of the harder problems is resources modified outside the pipeline.
Someone applies a quick change in the portal “just this once” and now your template diverges from
reality. The denyDelete and denyWriteAndDelete modes address this by creating Azure deny
assignments on every managed resource.
For resources that a separate team needs to manage independently — a Key Vault, a Log Analytics
workspace — you can carve out exceptions using --deny-settings-excluded-principals (by Entra
object ID) or --deny-settings-excluded-resources (by resource ID). The deny assignments are
created under the hood by Azure, not by you, and they count against the 2,000 deny assignment
limit per scope.
Limitations worth knowing about
A few edges to be aware of before committing stacks to production:
- 800 stacks per scope is the hard limit. Rarely a problem, but scope your stacks to match the management lifecycle of the resources, not for organisational convenience.
- Key Vault secrets are not deleted by stack cleanup, even with
deleteAll. Remove secrets manually or via the pipeline before tearing down a Key Vault managed by a stack. denyWriteAndDeleteis aggressive. It blocks even legitimate changes unless the principal is excluded. Start withdenyDeleteand escalate only where you need it.- No What-if support yet. You cannot preview a stack update the way you can a regular Bicep deployment. Test against a non-production environment first.
- Management group-scoped stacks can only deploy into their own management group or child subscriptions, not into a peer management group.
The takeaway
Deployment Stacks fill a genuine gap in native Azure IaC tooling. If your Bicep workflows have a
persistent resource-drift or cleanup problem, they are worth the small learning curve. Start at
resource group scope with a non-critical workload, use detachAll on unmanage while you learn
the behaviour, then tighten to deleteResources once you are confident the template is the source
of truth. The deny settings layer is a separate concern — useful, but independent of the cleanup
story.
Sources & further reading: Create and deploy Azure deployment stacks in Bicep, Quickstart: Create a deployment stack with Bicep, Azure deployment stacks integration with Azure Developer CLI, ARM Deployment Stacks now GA.