The Junior Climb hardcover mockup
Deployment Model in an Azure Landing Zone: Who Is Allowed to Click. Platform pipeline for management groups, hub, and policy. Workload pipeline for the spoke module. Portal as break-glass.

The spoke existed by lunch on Friday. Subscription in the right management group. Peering up. The app team was deploying by four.

Monday the private endpoint timed out. DNS zone in the hub, spoke VNet never linked. You already paid for that lesson in networking. The difference this time: nobody ran the spoke module. Someone with Owner at the management group clicked through the portal because the platform pipeline was “two PRs away.”

The landing zone on the diagram was fine. The landing zone that actually shipped was whatever a human could click before standup.

This is article nine, the last numbered piece in the series. Management groups, subscriptions, policy, identity, networking, logging, cost. None of that survives a Friday if the only path into Azure is still a privileged account and a browser.

Two pipelines, or you do not have a platform

Platform and workload are not folders in a repo. They are two identities that must not be able to do each other’s jobs.

The platform pipeline creates and updates the hierarchy, the hub, private DNS, the firewall, the Log Analytics workspace, policy assignments, budgets. Its service principal sits at the management group. Humans on that team use PIM. They do not keep Owner standing.

The workload pipeline consumes a spoke module the platform published: VNet, peering, UDRs, DNS links, diagnostic settings, the four tags, the subscription budget. Its service principal is Contributor in that subscription and nothing above it. If that identity can edit the hub firewall, identity already failed and Terraform will not save you.

I keep finding one pipeline that does both. It works for the first three subscriptions. Then a workload engineer needs a hotfix, the platform apply is locked, and Owner at the management group comes back “just this once.” That is the Friday spoke.

Split the modules before you split the teams

The repo in this series is already split on purpose. Hierarchy in one module. Policy initiatives in another. Structure first. Governance on that structure second.

module "management_group_hierarchy" {
source = "./modules/management-group-hierarchy"
root_parent_management_group_id = var.root_parent_management_group_id
management_group_prefix = "contoso"
}
module "policy_initiatives" {
source = "./modules/policy-initiatives"
location = "centralus"
management_group_ids = module.management_group_hierarchy.management_group_ids
allowed_locations = ["centralus", "eastus2"]
tag_policy_parameters = {
tag_name = "environment"
tag_value = "managed"
}
prod_allowed_skus = ["Standard_D4s_v5", "Standard_D8s_v5"]
nonprod_allowed_skus = ["Standard_B2s", "Standard_D2s_v5"]
}

That split is the whole argument. Changing a prod SKU list should not be in the same apply as creating contoso-platform. One 4,000-line module is how you get a PR that nobody will approve because the plan shows the firewall and a tag policy in the same diff.

Hub networking is a third module. Spoke is a fourth. Workload teams do not fork the hub. They call the spoke and pass the hub VNet id, the private DNS zone ids, and the workspace id. If they need a second peering because “it’s faster,” that is a platform change, not a spoke parameter.

Apply order matters. The policy module takes management_group_ids from the hierarchy module. Assigning prod-strict-baseline before contoso-prod exists is a failed apply, not a sequencing opinion. Hierarchy first. Policy second. Hub third. Spokes after the hub outputs exist: VNet id, firewall private IP, DNS zone ids, workspace id.

CAF / ALZ Terraform already thinks this way. You do not need their full catalog. You need the same seams: hierarchy, policy, connectivity, identity, landing zone. If your repo is still landing-zone.tf with everything in it, start by cutting hierarchy out. Policy next. Hub last, because that is the one people are scared to touch.

State is a control plane

Remote state for the platform, locked, in a subscription that is not a spoke. Workload state per subscription or per app, in a place the platform can read for peering outputs and the workload cannot write for hub resources.

I have walked into applies that used local state on a laptop, and into one Azure Storage account with a single key that every pipeline shared. The first one vanishes when someone is out. The second is Contributor on the whole estate with extra steps.

terraform plan in CI on every PR. Apply on merge to main, from the pipeline identity, not from a laptop. Protect the platform apply with an environment approval so a workload hotfix cannot ride along. If the only person who can apply is the one who built it, you have a hobby, and it will show up the first time they take leave.

Portal clicks that the pipeline does not know about will lose at the next apply, or the next apply will skip them and you will have two sources of truth. Pick one. The pipeline has to win. Break-glass is PIM, a ticket, and a follow-up PR that captures what you did. It is not a standing Owner role and a habit.

First apply vs everything that shows up later

The first platform apply should leave you with management groups, the hub, DNS, the workspace, and policy assigned at those groups. Logging already said not to wait on a remediation task for the firewall. Write the diagnostic setting. Cost already said the budget resource belongs in the same spoke module as the VNet.

DINE covers the Key Vault someone still created from the portal. It does not cover the hub. If your first apply is hierarchy plus a wiki that says “then we click the firewall,” you will click the firewall forever.

What the spoke module must include on day one, not as a phase two:

  • peering to the hub and the UDR that sends egress there
  • links to the private DNS zones the platform owns
  • diagnostic settings to the platform workspace
  • Environment, CostCenter, Owner, Application
  • a consumption budget that pages the owner

Skip any one of those and you will recreate a previous article as an incident. Missing DNS link is networking. Missing diagnostics is logging. Missing tags and budget is cost. Missing the deny on public IPs is policy. The module is how those stop being a checklist.

Who is allowed to click

Read access in the portal is fine. Most engineers need to see. Contributor at the subscription for a workload team is how the spoke module gets bypassed. Owner at the management group for anyone who is not the pipeline is how the Friday subscription happens.

Put the platform service principal in a group. Assign that group at the management group. PIM the humans. Policy still has to deny the things a Contributor will try: public IPs, missing tags, unapproved SKUs. Terraform is not a substitute for deny. Deny is what catches the click the pipeline did not make.

Sandbox is the exception you can live with if it is actually a sandbox management group, with a budget, and with deny_new_resource_creation waiting when you move that subscription to decommissioned. Sandbox that shares the hub and the platform workspace is how sub-sandbox-logs got deleted in a cleanup.

If the next subscription does not come from the module, stop

A landing zone is what you can recreate. Hierarchy, hub, policy, a spoke with tags, budget, logs, and DNS, from a pipeline identity that is not a person.

If the next team still needs a platform engineer to click peering and another one to link a DNS zone, the onboarding is still a ticket. The series was the design. This article is whether that design has a door that is not the portal.

Ship the hierarchy module. Ship the policy module against it. Publish a spoke module the workloads can actually call. Then the next subscription looks like the last one, on purpose.


Discover more from Randy Bordeaux

Subscribe to get the latest posts sent to your email.

Drop me a note, and let me know what you think

Discover more from Randy Bordeaux

Subscribe now to keep reading and get access to the full archive.

Continue reading