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
subnetsproperty 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 tableRemember
- 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.