Skip to content

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 = false in spoke-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-destroy must pass the same mcp.json as the spoke run that created the spoke. In the reference configuration repository spoke.yaml targets prod while spoke-destroy.yaml targets canary. Align them before you use them.

Check the entry file first. - mode: Apply means there is no approval stop before the destroy is applied. - whatIf: true means only the destroy plan is produced.

Run once with whatIf: true and read the destroy plan. Then run again with whatIf: 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.name was 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 azdo was 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.

  1. 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.
  2. 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.
  3. Check identity. The Entra ID groups SG_SPOKE_<wl>_<env>_* (prefix per platform) and the service principal SPN-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.
  4. Park the subscription. The platform has a decommissioned management 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 to decommissioned.subscription_ids in the primary region foundation.auto.tfvars, then run hub. See the caveat in Sandbox subscription.
  5. 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 destroy actions.
  • Connectivity subscription → Virtual WAN → Virtual network connections: the spoke VNet is gone.
  • The SG_SPOKE_<wl>_<env>_* groups and the SPN-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.