Management Group Hierarchy
I spend a lot of time on management group design at the start of enterprise Azure engagements, because mistakes here are expensive to fix. Management groups are the attachment point for Azure Policy and RBAC at scale — getting the hierarchy wrong means policies apply where they shouldn't, or don't apply where they should, and untangling that after workloads are deployed is painful.
The baseline hierarchy
The Cloud Adoption Framework's hierarchy is a solid starting point and I use it as the default:
Tenant Root Group
│
├── Platform
│ ├── Management (Log Analytics, Backup, Azure Monitor)
│ ├── Connectivity (Hub VNets, Firewall, ExpressRoute, DNS)
│ └── Identity (AD DS, Bastion, PIM config)
│
├── Landing Zones
│ ├── Corp (workloads requiring on-premises connectivity)
│ ├── Online (internet-facing, no on-prem dependency)
│ └── Sandbox (isolated, relaxed governance, auto-expiry)
│
├── Decommissioned (subscriptions pending cleanup)
│
└── (optional custom groups — see below)
The value of this structure is the policy inheritance model. Policies assigned at Landing Zones apply to everything under it. Policies at Corp only apply to corp workloads. This makes it possible to enforce "all prod workloads route through the firewall" without writing per-subscription rules.
When to create a new management group
This comes up in every engagement. The question is usually: "should the data platform be in Corp or Online, or should it get its own group?"
My rule: create a new management group only when a distinct set of policies needs to apply to a set of subscriptions that would contradict the policies on siblings.
Practically, that means:
| Scenario | My recommendation |
|---|---|
| Data platform that needs on-prem connectivity | Place in Corp — same network policy applies |
| Data platform with specific data residency or compliance requirements (e.g., financial data partition) | New management group under Landing Zones: Landing Zones/Corp/DataPlatform |
| SaaS product (internet-facing, no on-prem, but stricter egress controls than standard Online workloads) | New management group: Landing Zones/Online/SaaS with additional deny policies |
| Development/test environments that mirror production | Subdirectory under the appropriate zone: treat dev as a landing zone class, not a separate hierarchy branch |
| Outsourced operations requiring separate RBAC scope | New management group: Landing Zones/Managed with RBAC scoped to the outsourcing partner |
The anti-pattern I see most often: creating a new management group for every team or every application. This produces a deep hierarchy that makes policy management harder, not easier. Subscriptions are the right unit for application isolation — management groups are structural, not operational.
Placing specialised workloads
Data platforms (Azure Synapse, Databricks, Azure Data Factory):
I typically place these in Corp if they need to pull from on-premises sources. If they're purely cloud-native (ingesting from cloud services only), they go in Online. If the organisation has specific regulatory requirements around data classification or data residency, I create a DataPlatform sub-group with policies enforcing those controls:
Landing Zones
└── Corp
└── DataPlatform ← additional policies: deny cross-region data movement,
enforce diagnostic retention, require CMK encryption
SaaS products:
SaaS products often need looser network controls than internal corporate apps (they serve external customers, they need to call external APIs), but they have their own security requirements (PCI-DSS if payments, GDPR for EU customers). I place them under Online and add a sub-group when the policy differences are significant enough:
Landing Zones
└── Online
└── SaaS ← additional policies: require WAF, deny non-compliant
cipher suites, enforce HTTPS, no inbound 0.0.0.0/0
Platform engineering SaaS tools (Terraform Cloud, Artifactory, GitHub Enterprise Server):
These don't belong in Landing Zones — they're platform tooling. I place them under Platform:
Platform
├── Management
├── Connectivity
├── Identity
└── Tooling ← CI/CD tooling, artifact management, developer tools
Policy assignment strategy by level
The hierarchy defines where policies live. Here's how I think about the right level for each type of control:
| Control | Assign at | Reason |
|---|---|---|
| Require approved Azure regions | Tenant Root Group or Landing Zones | Non-negotiable for all workloads |
| Require specific tags (Environment, Owner, CostCenter) | Landing Zones | All workloads, but sandbox may be exempt |
| Deny public IP creation | Corp | Corp workloads should be private; Online workloads may need public IPs |
| Enforce routing through hub firewall | Corp | Corp only — Online workloads route differently |
| Require CMK encryption | Landing Zones/DataPlatform | Data workloads only |
| Deny most resource types (sandbox isolation) | Sandbox | Restrict what can be deployed in sandbox |
Terraform setup
# Management group structure
resource "azurerm_management_group" "root" {
display_name = "Tenant Root"
}
resource "azurerm_management_group" "platform" {
display_name = "Platform"
parent_management_group_id = azurerm_management_group.root.id
}
resource "azurerm_management_group" "landing_zones" {
display_name = "Landing Zones"
parent_management_group_id = azurerm_management_group.root.id
}
resource "azurerm_management_group" "corp" {
display_name = "Corp"
parent_management_group_id = azurerm_management_group.landing_zones.id
}
resource "azurerm_management_group" "online" {
display_name = "Online"
parent_management_group_id = azurerm_management_group.landing_zones.id
}
resource "azurerm_management_group" "sandbox" {
display_name = "Sandbox"
parent_management_group_id = azurerm_management_group.landing_zones.id
}
resource "azurerm_management_group" "decommissioned" {
display_name = "Decommissioned"
parent_management_group_id = azurerm_management_group.root.id
}
# Move a subscription into a management group
resource "azurerm_management_group_subscription_association" "my_sub" {
management_group_id = azurerm_management_group.corp.id
subscription_id = "/subscriptions/${var.subscription_id}"
}
RBAC across management groups
Management groups are also the right scope for platform team RBAC. I assign broad roles high in the hierarchy and narrow roles low:
| Role | Scope | Who |
|---|---|---|
Owner | Platform management group | Platform team only |
Contributor | Landing Zones (specific sub-group) | Delegated delivery teams |
Reader | Tenant Root Group | Security and audit team |
Cost Management Reader | Tenant Root Group | Finance team |
I never assign Owner at the tenant root — that's an emergency break-glass account only.