Cloud
Azure Deployment Stacks: validate unmanage and deny settings before an update
A production runbook for reviewing managed resources, unmanage behavior, deny settings, excluded identities, validation and rollback before updating an Azure Deployment Stack.
An Azure Deployment Stack update can deploy the intended template and still create the wrong operational outcome. The risk appears when a resource leaves the template, when unmanage behavior does not match the team’s intent, or when a deny setting prevents operations staff, a pipeline identity or a recovery tool from acting later.
The use case is a platform-prod stack that manages an application’s foundation: networking, identity, secrets, observability and a few shared resources. A pull request removes a resource that appears obsolete and tightens deny settings. Before promotion, the team must decide whether the stack can be updated, whether some resources should be detached, whether the template or deny scope needs correction, or whether the change must stay blocked until recovery is proven.
Freeze The Stack Contract
Start by writing down the intended boundary. A stack is more than a named Bicep template. It also carries an ownership boundary, a lifecycle decision for resources that leave that boundary, and protection rules that can affect operations after deployment.
stack:
name: platform-prod
scope: resource-group
resource_group: rg-platform-prod
template_ref: platform/main.bicep@8f42c31
pipeline_identity: mi-platform-deployer-prod
lifecycle_intent:
removed_managed_resources: detach_until_owner_confirms
shared_resources: never_delete_from_this_stack
application_resources: delete_only_with_data_owner_approval
protection_intent:
mode: denyDelete
apply_to_child_scopes: false
excluded_principals:
- mi-platform-deployer-prod
- breakglass-platform-operations
promotion_blockers:
- managed_resource_inventory_missing
- unmanage_action_not_reviewed
- excluded_principal_not_tested
- restore_or_recreate_path_missing
- post_change_probe_missing This contract removes two ambiguities: assuming that a resource absent from the new template should disappear, and treating a deny setting as a minor RBAC detail. Both decisions directly affect operability.
Capture Effective State Before The Diff
The repository describes intent. Azure describes reality. Capture the stack before the change, including provisioning state, protection settings and managed resources. Keep the export with deployment evidence.
az stack group show --resource-group rg-platform-prod --name platform-prod --output json > platform-prod.before.json
az stack group show --resource-group rg-platform-prod --name platform-prod --query "{state:provisioningState,actionOnUnmanage:actionOnUnmanage,denySettings:denySettings,resources:resources}" --output json Review the result instead of trusting the last green pipeline. A resource may have moved, been recreated under a different ID or changed outside IaC. A stack in an unexpected state is not a safe baseline for a destructive update.
Build A Managed Resource Inventory
The useful diff extends beyond Bicep lines. It compares four sets: resources managed now, resources declared next, shared resources, and resources holding state or data that IaC cannot simply reconstruct.
For every managed resource
Current Resource ID
Declaration present in the new template
Technical owner and data owner
Shared with another application
Inbound and outbound dependencies
Data or configuration not recreated by IaC
Expected action when it leaves the stack
Restore or recreation evidence
Classify the resource
KEEP remains in the template and stack
DETACH leaves the stack but remains in Azure
DELETE explicit, approved and recoverable deletion
BLOCK ownership, dependencies or recovery unknown A shared resource should not be deleted because it was placed in the wrong historical module. Conversely, detaching everything by default hides orphaned resources. Classification must be explicit and tied to an owner.
Separate The Diff From Unmanage Behavior
When a resource is no longer managed by the new definition, unmanage behavior determines what happens to it. The control point is therefore the combination of the diff and lifecycle action, not either item in isolation.
removed_from_template:
log_analytics_workspace:
stateful: true
shared: true
decision: detach
reason: workspace consumed by other workloads
follow_up: move ownership to observability stack
obsolete_dashboard:
stateful: false
shared: false
decision: delete
reason: replacement validated and no inbound dependency
rollback: redeploy previous dashboard module
key_vault:
stateful: true
shared: unknown
decision: block
reason: secret consumers and recovery path not proven Do not choose a global action because it fits most resources. If the batch mixes disposable resources and resources with data, split the change or reorganize ownership before updating the stack.
Treat Deny Settings As An Operations Path
A useful deny setting protects the stack against deletion or modification outside the controlled path. A poorly bounded deny also blocks legitimate work: incident response, rotation, recovery, a security pipeline or an approved deletion.
Required questions
Which deny mode is requested, and against which threat?
Does the deny apply to child scopes?
Which identities must still deploy, recover or delete?
Are excluded principals stable and expected object IDs?
Is there an emergency identity with logging and a procedure?
Do Azure Policy, locks and existing deny assignments overlap?
Block promotion
Excluded principal cannot be identified
Exclusion is broad at group or subscription level
Normal pipeline cannot update the stack
Recovery team has no tested path
Child scope protected without an owner inventory The important test is not whether an administrator can bypass the control. It is whether the intended identity can perform the intended operation while all other identities are rejected as expected.
Test The Scenario, Not Only The Template
Reproduce the update in a representative nonproduction scope. Include a removed resource, a retained resource, a denied attempt and an allowed operation by an excluded identity. Then compare effective resources, not only deployment status.
test_cases:
- name: managed_resource_stays_managed
expected: resource_present_and_owned_by_stack
- name: detached_resource_survives_update
expected: resource_present_without_stack_ownership
- name: approved_delete_removes_only_target
expected: target_absent_and_neighbors_unchanged
- name: unauthorized_delete_is_denied
expected: deny_evidence_recorded
- name: pipeline_identity_can_update
expected: update_succeeds_with_expected_identity
- name: rollback_path_recreates_configuration
expected: application_probe_returns_to_baseline
evidence:
- before_and_after_resource_ids
- deployment_operation_logs
- identity_object_ids
- deny_result
- application_and_observability_probes A successful deployment does not prove lifecycle correctness. The test must show that a detached resource still exists, deletion remains bounded and deny protection does not disable operations.
Add A Promotion Gate
The pipeline should refuse an update when it cannot explain removed resources or deny effects. Human approval remains useful, but it should review a concrete artifact.
Allow the update
Effective stack state captured
Current inventory compared with new template
Every removed resource classified DETACH or DELETE
BLOCK resources removed from the change batch
Unmanage action matches every resource lifecycle
Deny settings tested with allowed and denied identities
Application and observability validation ready
Before export and rollback path available
Reject the update
Stateful resource removed without an owner
Global destructive action applied to a mixed batch
Excluded principal added without justification
Child scope affected without an inventory
Rollback limited to redeploying the previous template The last point matters: redeploying the previous template may recreate a resource, but it does not automatically restore its data, secrets, history or dependencies.
Validate After The Update And Decide Rollback
After promotion, capture the stack again and verify expected Resource IDs. Test application paths, alerts, identity access and deny behavior. An unexplained difference should stop subsequent changes.
Keep the update
Every KEEP resource remains managed
Every DETACH resource exists and has a new owner
Only approved DELETE resources disappeared
Deny settings reject and allow the expected identities
Application probes and observability signals are healthy
Correct without global rollback
Metadata, owner or identity exclusion is incomplete
Detached resource is healthy but not yet adopted by a new stack
Protection is too broad and no resource was removed
Roll back or restore
Unapproved resource was deleted
Pipeline or emergency procedure is blocked by deny
Application regression correlates with the change
Stateful resource was recreated without its data
Before rollback
Freeze concurrent writes
Preserve before and after exports
Choose data restore, redeployment or targeted detach
Revalidate the application after each step Conclusion
An Azure Deployment Stack makes IaC ownership more explicit, but it does not decide which resources are disposable, shared or recoverable. A safe update connects the template diff, effective inventory, unmanage behavior, deny settings and operations identities.
The final decision must be clear: promote when every scope exit and access denial is proven, detach when ownership changes without deletion, split the batch when lifecycles differ, or roll back with a recovery plan when effective state no longer matches the contract.