ARM deployments in depth: modes, what-if, stacks

Microsoft Azure · Expert · 18 min

Knowing ARM's semantics prevents the classic IaC outages: deleted subnets, drift and orphaned resources.

A Bicep deployment becomes an ARM deployment resource. ARM orders resources by their dependencies and issues one PUT per resource, in parallel where it can. Long-running operations return 201/202 and ARM polls the provider's async operation URL until completion.

Semantics that bite experts:

  • A PUT sends the whole resource body. Properties you omit are reset to defaults or removed. The famous case: redeploying a VNet without its subnets property tries to delete subnets created elsewhere. Declare child collections in one place, or always include them.
  • Incremental mode (the default) leaves resources in the group that are not in the template alone. Complete mode deletes them, which is powerful but dangerous with modules spread across files.
  • What-if previews changes, but some providers report noisy property differences; read it, don't trust it blindly.
  • Deployment history is capped at 800 per scope; ARM automatically deletes old entries as you approach the limit.
  • Template limits include 4 MB compiled size and 800 resources (modules count as one each), which pushes large estates into modules.

Deployment stacks turn a set of deployed resources into a managed unit. On update, resources removed from the template are detached or deleted (--action-on-unmanage), and deny settings stop anyone outside the stack from changing or deleting them, closing the drift gap that plain incremental deployments leave.

Example

# Preview first: + create, ~ modify, - delete, = no change
az deployment group what-if -g rg-orders-prod -f main.bicep -p main.prod.bicepparam

# Manage the environment as a stack: removed resources get deleted,
# and nobody can delete stack resources outside the stack
az stack group create --name orders-prod -g rg-orders-prod \
  --template-file main.bicep --parameters main.prod.bicepparam \
  --action-on-unmanage deleteResources \
  --deny-settings-mode denyDelete \
  --deny-settings-excluded-principals "$PIPELINE_SP_OBJECT_ID"

az stack group show --name orders-prod -g rg-orders-prod \
  --query "resources[].id" -o tsv

# Inspect what a past deployment did
az deployment operation group list -g rg-orders-prod -n main \
  --query "[].{res:properties.targetResource.resourceName, op:properties.provisioningOperation, state:properties.provisioningState}" -o table

Remember

  • Every resource is a full-body PUT: omitted properties are reset
  • Incremental keeps extras; complete mode deletes them
  • What-if before every production change
  • Deployment stacks add cleanup of removed resources and deny settings

Watch out for

  • Defining subnets both inline in a VNet and as separate child resources, so each deployment fights the other.

Check yourself

  1. A pipeline redeploys a VNet template that has no subnets property. What can happen?
  2. What does a deployment stack's denyDelete setting provide that a plain deployment doesn't?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-stacks

Builds on

Unlocks

Nothing builds on this yet.