Starting Right Instead of Fixing Later
Every enterprise Azure deployment starts with someone creating a subscription and deploying resources. Six months later, there's a mess of untagged resources, overprivileged identities, and networking that nobody can diagram. Landing zones prevent this -- they're the foundation you lay before any workload deploys.
Microsoft's Cloud Adoption Framework defines the enterprise-scale architecture. It's opinionated, complex, and -- having implemented it twice -- worth the upfront investment for organizations with more than 50 engineers touching Azure.
Management Group Hierarchy
The architecture starts with management groups organizing subscriptions into a tree. Policy, RBAC, and cost management inherit down the tree, which means you define governance rules once and they apply everywhere beneath.
resource "azurerm_management_group" "root" {
display_name = "Contoso"
}
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" "connectivity" {
display_name = "Connectivity"
parent_management_group_id = azurerm_management_group.platform.id
}
resource "azurerm_management_group" "identity" {
display_name = "Identity"
parent_management_group_id = azurerm_management_group.platform.id
}
resource "azurerm_management_group" "management" {
display_name = "Management"
parent_management_group_id = azurerm_management_group.platform.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.root.id
}
The "Corp" and "Online" distinction separates internal-facing workloads from internet-facing ones. Corp workloads connect through the hub's firewall without direct internet exposure. Online workloads get their own internet-facing networking but still inherit baseline security policies.
The "Sandbox" group sits outside the landing zone hierarchy. Resources here get minimal policy enforcement -- teams can experiment freely without governance overhead. But sandbox subscriptions have hard spending limits and no connectivity to production networks.
Subscription Vending
In enterprise-scale, each workload gets its own subscription. Sounds excessive until you realize subscriptions are Azure's primary blast radius boundary. Misconfigured resources in one subscription can't affect another. RBAC is subscription-scoped by default. Cost tracking is automatic per subscription.
Manual subscription provisioning creates a weeks-long bottleneck. We automate it with a vending pipeline:
resource "azurerm_subscription" "workload" {
subscription_name = "sub-${var.team}-${var.workload}-${var.environment}"
billing_scope_id = var.billing_enrollment_account_id
tags = {
team = var.team
workload = var.workload
environment = var.environment
cost_center = var.cost_center
owner = var.owner_email
}
}
resource "azurerm_management_group_subscription_association" "placement" {
management_group_id = var.environment == "production" ?
azurerm_management_group.corp.id :
azurerm_management_group.sandbox.id
subscription_id = azurerm_subscription.workload.id
}
module "networking" {
source = "./modules/spoke-network"
subscription_id = azurerm_subscription.workload.id
hub_vnet_id = var.hub_vnet_id
address_space = var.assigned_cidr
environment = var.environment
}
A team requests a subscription through an internal portal. The pipeline creates the subscription, places it under the correct management group, configures spoke networking, applies baseline policies, and grants the requesting team Contributor access. What used to take two weeks now takes 15 minutes.
Hub-Spoke Networking
The connectivity subscription hosts the hub virtual network. Every workload subscription peers its spoke VNet to the hub. The hub contains Azure Firewall, VPN Gateway, Azure Bastion, and DNS infrastructure -- shared services that every workload needs but no individual workload should own.
resource "azurerm_virtual_network" "hub" {
name = "vnet-hub-eastus"
location = "eastus"
resource_group_name = azurerm_resource_group.connectivity.name
address_space = ["10.0.0.0/16"]
}
resource "azurerm_subnet" "firewall" {
name = "AzureFirewallSubnet"
resource_group_name = azurerm_resource_group.connectivity.name
virtual_network_name = azurerm_virtual_network.hub.name
address_prefixes = ["10.0.1.0/24"]
}
resource "azurerm_subnet" "bastion" {
name = "AzureBastionSubnet"
resource_group_name = azurerm_resource_group.connectivity.name
virtual_network_name = azurerm_virtual_network.hub.name
address_prefixes = ["10.0.2.0/24"]
}
resource "azurerm_virtual_network" "spoke_api" {
name = "vnet-api-prod-eastus"
location = "eastus"
resource_group_name = azurerm_resource_group.api_networking.name
address_space = ["10.10.0.0/20"]
}
resource "azurerm_virtual_network_peering" "spoke_to_hub" {
name = "peer-api-to-hub"
resource_group_name = azurerm_resource_group.api_networking.name
virtual_network_name = azurerm_virtual_network.spoke_api.name
remote_virtual_network_id = azurerm_virtual_network.hub.id
allow_forwarded_traffic = true
use_remote_gateways = true
}
resource "azurerm_virtual_network_peering" "hub_to_spoke" {
name = "peer-hub-to-api"
resource_group_name = azurerm_resource_group.connectivity.name
virtual_network_name = azurerm_virtual_network.hub.name
remote_virtual_network_id = azurerm_virtual_network.spoke_api.id
allow_forwarded_traffic = true
allow_gateway_transit = true
}
The `use_remote_gateways` flag on the spoke peering means all VPN/ExpressRoute traffic from on-premises routes through the hub's gateway, giving you centralized inspection and logging.
Policy-Driven Governance
Azure Policy enforces compliance automatically and continuously. Assign policies at the management group level so they cascade to every subscription beneath:
resource "azurerm_management_group_policy_assignment" "require_tags" {
name = "require-cost-tags"
management_group_id = azurerm_management_group.landing_zones.id
policy_definition_id = azurerm_policy_definition.require_tags.id
non_compliance_message {
content = "Resources must have costCenter, owner, and environment tags."
}
parameters = jsonencode({
tagNames = {
value = ["costCenter", "owner", "environment"]
}
})
}
resource "azurerm_management_group_policy_assignment" "deny_public_ip_corp" {
name = "deny-public-ip-corp"
management_group_id = azurerm_management_group.corp.id
policy_definition_id = "/providers/Microsoft.Authorization/policyDefinitions/6c112d4e-5bc7"
non_compliance_message {
content = "Public IP addresses are not allowed in Corp landing zones. Use Azure Firewall for internet egress."
}
}
resource "azurerm_management_group_policy_assignment" "audit_encryption" {
name = "audit-disk-encryption"
management_group_id = azurerm_management_group.root.id
policy_definition_id = "/providers/Microsoft.Authorization/policyDefinitions/0961003e-5a0a"
}
The tag requirement prevents the "untagged resource graveyard" that plagues unmanaged environments. The public IP denial on Corp landing zones ensures internal workloads stay internal. The encryption audit at the root catches any unencrypted disks across the entire hierarchy.
Implementation Timeline
Realistic enterprise-scale deployment takes 8-12 weeks for the platform foundation. Don't try to compress this -- shortcuts in landing zone setup create years of technical debt.
Weeks 1-2: management group hierarchy, subscription vending pipeline, baseline policy assignments. The governance skeleton that everything builds on.
Weeks 3-4: hub networking, Azure Firewall configuration, VPN Gateway for on-premises connectivity. The most complex phase if there's existing on-prem infrastructure to integrate. ExpressRoute circuits take weeks to provision through the carrier, so order these early.
Weeks 5-6: identity integration (Azure AD Conditional Access, Privileged Identity Management), management subscription (Log Analytics workspace, Azure Monitor, Defender for Cloud).
Weeks 7-8: first workload landing zone. Deploy one real application through the full pipeline: vend a subscription, peer networking, apply policies, deploy the workload, verify monitoring and alerting end-to-end.
Remaining weeks: iteration based on what the first workload revealed. Fix policy conflicts that blocked legitimate deployments. Adjust firewall rules for application-specific traffic patterns. Onboard additional teams and train them on the subscription request process.
Don't try to design the perfect landing zone upfront. Deploy the minimum viable version, get one workload running in production, and iterate. An imperfect landing zone running real workloads today beats a perfect design document reviewed for six months.