Delete a Spoke¶
Standard change: Deletion of workload/spokes/application landing zone (spoke canvas)
Effect: everything the spoke canvas created in the subscription and in the hub is destroyed, including the networking. The subscription itself is not cancelled. Its tags and its name are not reverted. The management-group association can be removed as well (see What is destroyed).
Data loss. This destroys the spoke's resource groups, Key Vault and storage account (
prevent_deletion_if_contains_resources = falseinspoke-canvas/deploy/spoke/providers.tf). Resources the workload team deployed inside those resource groups are deleted too. Get written confirmation from the workload owner first.
Request fields¶
| Field | Use |
|---|---|
| Name | Identifies the spoke file(s): workloadShortName, file name |
| Rationale | Change record |
| Environment | Which spoke environment(s) to remove (dev, tst, acc, prd, shared) |
| Usage type (production data yes/no) | If yes: confirm data retention/backup before deleting |
| Comments |
Steps¶
1. Find the file and check what depends on the spoke¶
- Spoke file:
<env-folder>/spokes/**/<file>.json. - Search the configuration repository for the spoke's address ranges and name, in:
- IP groups (
ip_groups/*.json), - firewall rules (
firewall_rule_configuration/*.json), - Application Gateway JSON.
These are hub resources. The spoke destroy does not remove them.
2. Run the destroy pipeline for that file only¶
Run spoke-destroy with spokeGlob set to the exact file, for example team-a/canvas_app1_dev.json.
- Azure DevOps: Pipelines →
spoke-destroy→ Run pipeline →spokeGlob=team-a/canvas_app1_dev.json. - GitHub Actions: Actions →
spoke-destroy→ Run workflow →spokeGlob=team-a/canvas_app1_dev.json.
The reference configuration repository uses none as the default spokeGlob, so a run without input matches nothing
and fails in Prepare ("did not match any spoke JSON files") before anything is destroyed. Keep that default.
Use the same platform environment as the deployment.
spoke-destroymust pass the samemcp.jsonas thespokerun that created the spoke. In the reference configuration repositoryspoke.yamltargetsprodwhilespoke-destroy.yamltargetscanary. Align them before you use them.Check the entry file first. -
mode: Applymeans there is no approval stop before the destroy is applied. -whatIf: truemeans only the destroy plan is produced.Run once with
whatIf: trueand read the destroy plan. Then run again withwhatIf: false. See Pipelines and workflows.
What is destroyed¶
Everything in the spoke state spoke_<wl>_<env>.tfstate:
- the virtual hub connection, in the connectivity subscription;
- the virtual network(s), subnets, NSGs, rules, route tables and associations;
- the private DNS zone and its hub link (if
privatedns.namewas set); - the resource group(s) and everything in them, including the Key Vault, storage account and Log Analytics workspace;
- the Entra ID groups
SG_SPOKE_<wl>_<env>_*, the PIM settings, the service principal and its role assignments; - the Azure DevOps repository and service connection (if
azdowas set); - the management-group association if it is in the spoke state
(
modules/subscription_managementgroup/main.tf). Deleting an association normally puts the subscription back in the tenant's default management group. Check where the subscription ends up afterwards. Also see the known issue about this association in Known issues.
Not changed:
- the subscription layer (
<wl>_<env>_Subscription.tfstate). Through the pipeline the subscription is never cancelled (Invoke-XebiaSpokeApplyAction.ps1: subscription destroy is explicitly blocked); - the subscription tags and the subscription name. They are set with
azapi_update_resource/azapi_resource_action, which do not revert on destroy.
3. Clean up hub references¶
In a follow-up pull request, remove the firewall rules, IP group entries and Application Gateway configuration you
found in step 1. Then run hub (see Firewall rules and
Application Gateway).
4. Decommission the subscription¶
Retire the subscription in this order, so you keep a trace of its status until it is gone.
- Check that it is empty. Open the subscription in the Azure portal and check the resource groups. After the destroy, no spoke resources should remain. Delete anything that is left, such as resources the workload team created outside the spoke resource groups. Check that no workload is active.
- Check the hub connection. In the connectivity subscription, open the Virtual WAN and check Virtual network connections: the spoke's connection must be gone. The destroy removes it. If the spoke was removed by hand, remove it yourself for complete network isolation.
- Check identity. The Entra ID groups
SG_SPOKE_<wl>_<env>_*(prefix per platform) and the service principalSPN-SPOKE-<wl>-<env>must be gone. The destroy removes them. If it didn't run, remove them by hand. This needs a role that can delete groups and app registrations. - Park the subscription. The platform has a
decommissionedmanagement group (management_group_structure.decommissioned). Move the subscription there, so everybody can see that it is on its way out. To do this with code, add its ID todecommissioned.subscription_idsin the primary regionfoundation.auto.tfvars, then runhub. See the caveat in Sandbox subscription. - Cancel the subscription in the portal (subscription overview > Cancel subscription) or through your billing process (EA/MCA/CSP). Azure then keeps the subscription for a grace period before it deletes the subscription and its data permanently. Note the cancellation date in the change record, and check at the end of the grace period that the subscription is gone. Check the length of the period in Microsoft's documentation, because it depends on the type of subscription.
5. Remove the spoke file¶
Delete the spoke JSON (or rename it to .json.ignore) and merge. If you don't, the next spoke run with a broad glob
recreates the spoke.
6. Release the IP range¶
Remove the spoke's address range from the IP plan, so it can be allocated again. Do this only when you are sure that nothing in the hub (firewall rules, IP groups, routes, DNS) refers to the range any more.
Verify¶
- The spoke-destroy Apply job is green, and its plan showed only
destroyactions. - Connectivity subscription → Virtual WAN → Virtual network connections: the spoke VNet is gone.
- The
SG_SPOKE_<wl>_<env>_*groups and theSPN-SPOKE-<wl>-<env>app registration are gone.
Roll back¶
A destroy cannot be undone. Running New spoke with the same file recreates the canvas, but storage data and anything the workload deployed are gone.
Key Vault is a special case. The spoke provider sets purge_soft_delete_on_destroy = true and
recover_soft_deleted_key_vaults = true (spoke-canvas/deploy/spoke/providers.tf). Purge protection is enabled when
SoftDeleteRetentionInDays is not 0 (modules/keyvault/main.tf). With purge protection the vault stays soft-deleted
after the destroy. A re-create with the same name recovers that vault, with its old keys and secrets, instead of
creating an empty one.