Architecture Azure

Designing an Azure Landing Zone with the Cloud Adoption Framework

A landing zone is the pre-provisioned, governed environment your workloads land in — networking, identity, policy, and management already wired up so application teams move fast without re-litigating security on every project. Microsoft’s Cloud Adoption Framework (CAF) codifies this into the Azure landing zone architecture. This guide builds one the way it’s done in regulated enterprises.

Azure hub-and-spoke landing zone

The eight design areas

Every enterprise-scale landing zone is a set of decisions across eight areas. Get these right and the rest is implementation detail:

  1. Azure billing & Entra tenant — enrollment, tenant topology.
  2. Identity & access management — Entra ID, RBAC, PIM.
  3. Resource organization — management groups & subscriptions.
  4. Network topology & connectivity — hub-spoke or Virtual WAN.
  5. Security — Defender for Cloud, encryption, secrets.
  6. Management — monitoring, backup, update management.
  7. Governance — Azure Policy, cost controls.
  8. Platform automation & DevOps — IaC, pipelines, CI/CD.

Step 1 — Management group hierarchy

Subscriptions are the unit of scale; management groups are how you apply policy and RBAC once and inherit everywhere. The CAF reference hierarchy:

Tenant Root Group
└── Contoso (top-level MG)
    ├── Platform
    │   ├── Identity        (domain controllers, Entra Connect)
    │   ├── Management      (Log Analytics, automation)
    │   └── Connectivity    (hub VNet, firewall, DNS)
    ├── Landing Zones
    │   ├── Corp            (internal, no public ingress)
    │   └── Online          (internet-facing)
    ├── Decommissioned
    └── Sandbox

Create it with Bicep at the tenant scope:

targetScope = 'managementGroup'

param topLevelMgId string = 'contoso'

resource platform 'Microsoft.Management/managementGroups@2023-04-01' = {
  name: 'platform'
  properties: {
    displayName: 'Platform'
    details: { parent: { id: tenantResourceId('Microsoft.Management/managementGroups', topLevelMgId) } }
  }
}

Why this matters: policies assigned at Landing Zones (e.g. “deny public IPs on NICs”, “require tags”) flow to every current and future subscription beneath it. New teams inherit guardrails on day one.

Step 2 — Subscription democratization

Hand each workload (or environment) its own subscription. Subscriptions are a scale unit and a billing/blast-radius boundary — not something to hoard. A typical split:

Subscription Lives under Purpose
sub-connectivity Platform/Connectivity Hub VNet, Firewall, ExpressRoute
sub-management Platform/Management Log Analytics, Automation, backup vault
sub-identity Platform/Identity Domain controllers, Entra Connect
sub-prod-corp Landing Zones/Corp Production workloads, no public ingress
sub-prod-online Landing Zones/Online Internet-facing apps

Step 3 — Hub-and-spoke networking

The hub holds shared network services (firewall, gateways, DNS, Bastion). Spokes hold workloads and peer to the hub. All spoke-to-spoke and spoke-to-internet traffic is forced through the firewall.

Terraform for the hub VNet + Azure Firewall:

resource "azurerm_virtual_network" "hub" {
  name                = "vnet-hub-eus"
  resource_group_name = azurerm_resource_group.connectivity.name
  location            = "eastus"
  address_space       = ["10.10.0.0/16"]
}

resource "azurerm_subnet" "firewall" {
  name                 = "AzureFirewallSubnet" # name is mandatory & exact
  resource_group_name  = azurerm_resource_group.connectivity.name
  virtual_network_name = azurerm_virtual_network.hub.name
  address_prefixes     = ["10.10.1.0/26"]
}

resource "azurerm_firewall" "hub" {
  name                = "afw-hub-eus"
  resource_group_name = azurerm_resource_group.connectivity.name
  location            = "eastus"
  sku_name            = "AZFW_VNet"
  sku_tier            = "Standard"
  ip_configuration {
    name                 = "ipc"
    subnet_id            = azurerm_subnet.firewall.id
    public_ip_address_id = azurerm_public_ip.fw.id
  }
}

Peer a spoke to the hub (both directions, with gateway transit so spokes use the hub’s VPN/ER gateway):

resource "azurerm_virtual_network_peering" "spoke_to_hub" {
  name                         = "prod-to-hub"
  resource_group_name          = azurerm_resource_group.prod.name
  virtual_network_name         = azurerm_virtual_network.prod.name
  remote_virtual_network_id    = azurerm_virtual_network.hub.id
  allow_forwarded_traffic      = true
  use_remote_gateways          = true
}

Then force spoke egress through the firewall with a route table (UDR) whose default route points at the firewall’s private IP:

resource "azurerm_route" "default_to_fw" {
  name                   = "default-via-firewall"
  route_table_name       = azurerm_route_table.spoke.name
  resource_group_name    = azurerm_resource_group.prod.name
  address_prefix         = "0.0.0.0/0"
  next_hop_type          = "VirtualAppliance"
  next_hop_in_ip_address = azurerm_firewall.hub.ip_configuration[0].private_ip_address
}

Step 4 — Governance with Azure Policy

Policy is how a landing zone stays compliant. Assign initiatives (policy sets) at the management-group scope. Three you almost always want:

resource denyPublicIp 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
  name: 'deny-public-ip'
  scope: managementGroup()
  properties: {
    policyDefinitionId: tenantResourceId(
      'Microsoft.Authorization/policyDefinitions',
      '83a86a26-fd1f-447c-b59d-e51f44264114') // built-in: not allow public IP on NIC
    enforcementMode: 'Default'
  }
}

Step 5 — Identity & least privilege

Step 6 — Platform automation

Everything above is IaC. Structure it so the platform team owns the hierarchy, policy, and hub, while application teams own their spokes:

platform/        # MG hierarchy, policy, hub network, Log Analytics  (platform team)
landing-zones/   # per-workload spoke modules                         (app teams, via PR)
modules/         # shared, versioned modules (network, monitoring)

Deploy through pipelines with a service principal (or workload identity federation) that has Owner on the relevant management group — gated behind PR review and a plan approval.

Enterprise scenario

A financial-services client deployed the CAF reference hierarchy with the standard Deny-PublicIP and Deny-PublicEndpoint initiatives at the Corp management group, then ran a wave of platform-team Bicep deployments to stand up shared services. Half the deployments failed at the DeployIfNotExists remediation step. The deny policy was blocking the very Private Endpoints that other DINE policies were trying to create for Key Vault and Storage — and DINE remediation tasks run under a managed identity that the deny effect evaluates like any other principal. The result was a deadlock: the platform couldn’t bootstrap its own private connectivity under its own guardrails.

The fix was not to weaken the deny policy but to scope it correctly. Private Endpoints are not public endpoints, so the team narrowed the deny rule with a notIn exclusion on the platform resource group and assigned the remediation identity an explicit role at the right scope:

resource remediation 'Microsoft.PolicyInsights/remediations@2021-10-01' = {
  name: 'remediate-pe-storage'
  scope: subscription()
  properties: {
    policyAssignmentId: deployPrivateEndpoint.id
    resourceDiscoveryMode: 'ReEvaluateCompliance'
    failureThreshold: { percentage: 10 }
  }
}

The broader lesson: order policy effects deliberately. Deny evaluates before DeployIfNotExists, so a deny that’s too broad silently starves your auto-remediation. Always dry-run new initiatives in enforcementMode: 'DoNotEnforce' against the platform subscriptions first, read the compliance results, then flip enforcement on.

Landing-zone readiness checklist

Where to go next

Wire this landing zone to a CI/CD pipeline, layer Microsoft Defender for Cloud regulatory compliance dashboards on top, and adopt the Well-Architected Framework to pressure-test each workload across reliability, security, cost, operational excellence, and performance. The landing zone is the foundation — WAF keeps what lands on it healthy.

AzureLanding ZoneCAFGovernanceBicepTerraformHub-Spoke

Comments

Keep Reading