Cloud Architecture15 min read

Azure Landing Zones: Technical Deep Dive for Architects

Bicep code, policy definitions, and implementation patterns

IM

Iulian Mihai

Principal Cloud Architect & AI Innovation Leader

Azure Landing Zones Technical Architecture with Bicep Code

This is the implementation companion to my Principal Architect's Guide to Azure Landing Zones. If you're a fellow architect looking for concrete Bicep code, policy definitions, and management group structures — this is for you.

⚠️ Prerequisites

  • • Azure subscription with Owner or User Access Administrator role
  • • Azure CLI or PowerShell with Az module installed
  • • Bicep CLI (comes with Azure CLI 2.20.0+)
  • • Understanding of Azure Resource Manager and RBAC

Management Group Hierarchy: My Recommended Structure

The foundation of any Landing Zone is the management group hierarchy. Here's the structure I implement for most enterprise clients:

Tenant Root Group
└── Organization Root (e.g., "contoso")
    ├── Platform
    │   ├── Management
    │   │   └── [Management Subscription]
    │   ├── Connectivity
    │   │   └── [Connectivity Subscription]
    │   └── Identity
    │       └── [Identity Subscription]
    ├── Landing Zones
    │   ├── Corp
    │   │   ├── [Production Subscriptions]
    │   │   └── [Non-Production Subscriptions]
    │   └── Online
    │       ├── [Public-facing workloads]
    │       └── [Internet-exposed services]
    ├── Sandbox
    │   └── [Developer sandboxes]
    └── Decommissioned
        └── [Subscriptions pending deletion]

Why This Structure?

  • Platform separation — Core infrastructure (identity, networking, monitoring) is isolated from workloads
  • Corp vs Online — Different security postures for internal vs internet-facing workloads
  • Sandbox isolation — Developers can experiment without affecting production policies
  • Clear decommissioning path — No orphaned subscriptions floating around

Bicep: Creating the Management Group Hierarchy

Here's the Bicep code to deploy this hierarchy:

// management-groups.bicep
targetScope = 'tenant'

@description('The organization prefix (e.g., contoso)')
param orgPrefix string

@description('The location for deployment metadata')
param location string = 'westeurope'

// Root Management Group
resource rootMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: orgPrefix
  properties: {
    displayName: '${orgPrefix} Organization'
  }
}

// Platform Management Groups
resource platformMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: '${orgPrefix}-platform'
  properties: {
    displayName: 'Platform'
    details: {
      parent: {
        id: rootMg.id
      }
    }
  }
}

resource managementMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: '${orgPrefix}-management'
  properties: {
    displayName: 'Management'
    details: {
      parent: {
        id: platformMg.id
      }
    }
  }
}

resource connectivityMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: '${orgPrefix}-connectivity'
  properties: {
    displayName: 'Connectivity'
    details: {
      parent: {
        id: platformMg.id
      }
    }
  }
}

resource identityMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: '${orgPrefix}-identity'
  properties: {
    displayName: 'Identity'
    details: {
      parent: {
        id: platformMg.id
      }
    }
  }
}

// Landing Zone Management Groups
resource landingZonesMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: '${orgPrefix}-landingzones'
  properties: {
    displayName: 'Landing Zones'
    details: {
      parent: {
        id: rootMg.id
      }
    }
  }
}

resource corpMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: '${orgPrefix}-corp'
  properties: {
    displayName: 'Corp'
    details: {
      parent: {
        id: landingZonesMg.id
      }
    }
  }
}

resource onlineMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: '${orgPrefix}-online'
  properties: {
    displayName: 'Online'
    details: {
      parent: {
        id: landingZonesMg.id
      }
    }
  }
}

// Sandbox
resource sandboxMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: '${orgPrefix}-sandbox'
  properties: {
    displayName: 'Sandbox'
    details: {
      parent: {
        id: rootMg.id
      }
    }
  }
}

// Decommissioned
resource decommissionedMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: '${orgPrefix}-decommissioned'
  properties: {
    displayName: 'Decommissioned'
    details: {
      parent: {
        id: rootMg.id
      }
    }
  }
}

output rootMgId string = rootMg.id
output platformMgId string = platformMg.id
output landingZonesMgId string = landingZonesMg.id

Deploy with:

az deployment tenant create \
  --location westeurope \
  --template-file management-groups.bicep \
  --parameters orgPrefix='contoso'

Azure Policies: The Essential Set

These are the policies I deploy on every Landing Zone engagement. They're battle-tested across UN agencies, financial services, and Fortune 500 enterprises.

1. Deny Public IP on NICs

Prevents accidental exposure of VMs to the internet:

// policy-deny-public-ip.bicep
targetScope = 'managementGroup'

resource policyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  name: 'deny-public-ip-on-nic'
  properties: {
    displayName: 'Deny Public IP addresses on Network Interfaces'
    description: 'Prevents creation of NICs with public IP addresses attached'
    policyType: 'Custom'
    mode: 'All'
    metadata: {
      version: '1.0.0'
      category: 'Network'
    }
    policyRule: {
      if: {
        allOf: [
          {
            field: 'type'
            equals: 'Microsoft.Network/networkInterfaces'
          }
          {
            count: {
              field: 'Microsoft.Network/networkInterfaces/ipConfigurations[*]'
              where: {
                field: 'Microsoft.Network/networkInterfaces/ipConfigurations[*].publicIPAddress.id'
                notEquals: ''
              }
            }
            greaterOrEquals: 1
          }
        ]
      }
      then: {
        effect: 'deny'
      }
    }
  }
}

2. Enforce Resource Tagging

Critical for cost management and compliance. This policy requires specific tags on all resource groups:

// policy-require-tags.bicep
targetScope = 'managementGroup'

@description('Required tag names')
param requiredTags array = [
  'CostCenter'
  'Owner'
  'Environment'
  'Application'
]

resource policyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  name: 'require-rg-tags'
  properties: {
    displayName: 'Require mandatory tags on Resource Groups'
    description: 'Enforces required tags on all resource groups for governance'
    policyType: 'Custom'
    mode: 'All'
    metadata: {
      version: '1.0.0'
      category: 'Tags'
    }
    parameters: {
      tagName: {
        type: 'String'
        metadata: {
          displayName: 'Tag Name'
          description: 'Name of the required tag'
        }
      }
    }
    policyRule: {
      if: {
        allOf: [
          {
            field: 'type'
            equals: 'Microsoft.Resources/subscriptions/resourceGroups'
          }
          {
            field: '[concat(\'tags[\', parameters(\'tagName\'), \']\')]'
            exists: false
          }
        ]
      }
      then: {
        effect: 'deny'
      }
    }
  }
}

// Create a policy initiative (policy set) for all required tags
resource policySetDefinition 'Microsoft.Authorization/policySetDefinitions@2021-06-01' = {
  name: 'require-mandatory-tags'
  properties: {
    displayName: 'Require Mandatory Tags Initiative'
    description: 'Enforces all mandatory tags on resource groups'
    policyType: 'Custom'
    metadata: {
      version: '1.0.0'
      category: 'Tags'
    }
    policyDefinitions: [for tag in requiredTags: {
      policyDefinitionId: policyDefinition.id
      parameters: {
        tagName: {
          value: tag
        }
      }
    }]
  }
}

3. Allowed Locations (EU Data Sovereignty)

For EU-based organizations, this ensures data never leaves approved regions:

// policy-allowed-locations.bicep
targetScope = 'managementGroup'

@description('Allowed Azure regions for EU data sovereignty')
param allowedLocations array = [
  'westeurope'
  'northeurope'
  'germanywestcentral'
  'francecentral'
  'swedencentral'
]

resource policyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  name: 'allowed-locations-eu'
  properties: {
    displayName: 'Allowed Locations - EU Only'
    description: 'Restricts resource deployment to EU regions only'
    policyType: 'Custom'
    mode: 'Indexed'
    metadata: {
      version: '1.0.0'
      category: 'General'
    }
    parameters: {
      listOfAllowedLocations: {
        type: 'Array'
        metadata: {
          displayName: 'Allowed locations'
          description: 'The list of allowed locations for resources'
          strongType: 'location'
        }
        defaultValue: allowedLocations
      }
    }
    policyRule: {
      if: {
        allOf: [
          {
            field: 'location'
            notIn: '[parameters(\'listOfAllowedLocations\')]'
          }
          {
            field: 'location'
            notEquals: 'global'
          }
          {
            field: 'type'
            notEquals: 'Microsoft.AzureActiveDirectory/b2cDirectories'
          }
        ]
      }
      then: {
        effect: 'deny'
      }
    }
  }
}

4. Deny Storage Without HTTPS

// policy-storage-https.bicep
targetScope = 'managementGroup'

resource policyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  name: 'storage-require-https'
  properties: {
    displayName: 'Storage accounts should require HTTPS'
    description: 'Ensures all storage accounts require secure transfer (HTTPS)'
    policyType: 'Custom'
    mode: 'Indexed'
    metadata: {
      version: '1.0.0'
      category: 'Storage'
    }
    policyRule: {
      if: {
        allOf: [
          {
            field: 'type'
            equals: 'Microsoft.Storage/storageAccounts'
          }
          {
            field: 'Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly'
            notEquals: true
          }
        ]
      }
      then: {
        effect: 'deny'
      }
    }
  }
}

CAF Modules vs Custom: My Recommendation

The Azure Cloud Adoption Framework (CAF) provides Enterprise-Scale landing zone modules. Here's my honest assessment:

✅ Use CAF Modules When:

  • • Greenfield deployment with no existing infrastructure
  • • Standard enterprise requirements (no unusual compliance)
  • • Limited internal Bicep/Terraform expertise
  • • You want Microsoft-supported patterns
  • • Faster time-to-production is priority

⚠️ Go Custom When:

  • • Brownfield with existing management groups/policies
  • • Highly specific compliance requirements (GDPR, PCI-DSS)
  • • Multi-cloud strategy requiring consistency
  • • Strong internal platform engineering team
  • • Need fine-grained control over every resource

My Hybrid Approach

In practice, I often use a hybrid:

  1. Start with CAF structure — Use the management group hierarchy pattern
  2. Customize policies — CAF policies as baseline, then add custom ones
  3. Own your networking — Hub-spoke is universal, but implementation varies
  4. Abstract the modules — Create internal modules that wrap CAF or custom code

Hub-Spoke Network Topology

Here's a production-ready hub-spoke network setup:

// hub-network.bicep
targetScope = 'subscription'

@description('Azure region for deployment')
param location string = 'westeurope'

@description('Hub VNet address space')
param hubAddressPrefix string = '10.0.0.0/16'

@description('Gateway subnet prefix')
param gatewaySubnetPrefix string = '10.0.0.0/24'

@description('Azure Firewall subnet prefix')
param firewallSubnetPrefix string = '10.0.1.0/24'

@description('Bastion subnet prefix')
param bastionSubnetPrefix string = '10.0.2.0/24'

resource hubRg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
  name: 'rg-connectivity-hub'
  location: location
  tags: {
    CostCenter: 'Platform'
    Owner: 'Platform Team'
    Environment: 'Production'
    Application: 'Network Hub'
  }
}

module hubVnet 'modules/vnet.bicep' = {
  scope: hubRg
  name: 'hub-vnet'
  params: {
    name: 'vnet-hub-${location}'
    location: location
    addressPrefixes: [hubAddressPrefix]
    subnets: [
      {
        name: 'GatewaySubnet'
        addressPrefix: gatewaySubnetPrefix
      }
      {
        name: 'AzureFirewallSubnet'
        addressPrefix: firewallSubnetPrefix
      }
      {
        name: 'AzureBastionSubnet'
        addressPrefix: bastionSubnetPrefix
      }
    ]
  }
}

// Azure Firewall for centralized egress
module firewall 'modules/firewall.bicep' = {
  scope: hubRg
  name: 'hub-firewall'
  params: {
    name: 'afw-hub-${location}'
    location: location
    subnetId: hubVnet.outputs.subnets[1].id
    sku: 'Premium' // Use Premium for IDPS and TLS inspection
  }
}

// VPN Gateway for hybrid connectivity
module vpnGateway 'modules/vpn-gateway.bicep' = {
  scope: hubRg
  name: 'hub-vpn-gateway'
  params: {
    name: 'vpng-hub-${location}'
    location: location
    subnetId: hubVnet.outputs.subnets[0].id
    sku: 'VpnGw2AZ' // Zone-redundant
  }
}

output hubVnetId string = hubVnet.outputs.vnetId
output firewallPrivateIp string = firewall.outputs.privateIp

Diagnostic Settings: Centralized Logging

Every enterprise landing zone needs centralized logging. Here's how to enforce it with policy:

// policy-diagnostic-settings.bicep
targetScope = 'managementGroup'

@description('Log Analytics Workspace ID for centralized logging')
param logAnalyticsWorkspaceId string

resource policyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  name: 'deploy-diagnostic-settings'
  properties: {
    displayName: 'Deploy Diagnostic Settings to Log Analytics'
    description: 'Automatically configures diagnostic settings for supported resources'
    policyType: 'Custom'
    mode: 'Indexed'
    metadata: {
      version: '1.0.0'
      category: 'Monitoring'
    }
    parameters: {
      logAnalytics: {
        type: 'String'
        metadata: {
          displayName: 'Log Analytics Workspace'
          description: 'The Log Analytics Workspace ID to send diagnostics to'
          strongType: 'omsWorkspace'
        }
        defaultValue: logAnalyticsWorkspaceId
      }
    }
    policyRule: {
      if: {
        field: 'type'
        equals: 'Microsoft.KeyVault/vaults'
      }
      then: {
        effect: 'deployIfNotExists'
        details: {
          type: 'Microsoft.Insights/diagnosticSettings'
          existenceCondition: {
            allOf: [
              {
                field: 'Microsoft.Insights/diagnosticSettings/workspaceId'
                equals: '[parameters(\'logAnalytics\')]'
              }
            ]
          }
          roleDefinitionIds: [
            '/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa'
            '/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293'
          ]
          deployment: {
            properties: {
              mode: 'incremental'
              template: {
                '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
                contentVersion: '1.0.0.0'
                parameters: {
                  resourceName: { type: 'string' }
                  logAnalytics: { type: 'string' }
                }
                resources: [
                  {
                    type: 'Microsoft.KeyVault/vaults/providers/diagnosticSettings'
                    apiVersion: '2021-05-01-preview'
                    name: '[concat(parameters(\'resourceName\'), \'/Microsoft.Insights/setByPolicy\')]'
                    properties: {
                      workspaceId: '[parameters(\'logAnalytics\')]'
                      logs: [
                        { category: 'AuditEvent', enabled: true }
                        { category: 'AzurePolicyEvaluationDetails', enabled: true }
                      ]
                      metrics: [
                        { category: 'AllMetrics', enabled: true }
                      ]
                    }
                  }
                ]
              }
              parameters: {
                resourceName: { value: '[field(\'name\')]' }
                logAnalytics: { value: '[parameters(\'logAnalytics\')]' }
              }
            }
          }
        }
      }
    }
  }
}

Implementation Checklist

🚀 Landing Zone Deployment Order

  1. 1Management Groups — Create hierarchy at tenant level
  2. 2Policy Definitions — Deploy custom policies to root MG
  3. 3Management Subscription — Log Analytics, Automation, Defender
  4. 4Connectivity Subscription — Hub VNet, Firewall, VPN/ExpressRoute
  5. 5Identity Subscription — Domain Controllers, AD Connect (if hybrid)
  6. 6Policy Assignments — Assign policies to appropriate MGs
  7. 7Landing Zone Subscriptions — Vend new subscriptions with guardrails

Common Pitfalls (And How I Avoid Them)

❌ Pitfall: Policy in Audit Mode Forever

Many teams deploy policies in "Audit" mode and never switch to "Deny". Set a deadline: audit for 2 weeks, then enforce.

❌ Pitfall: No Exemption Process

Policies without exemption paths create shadow IT. Build a governance process for time-limited exemptions with automatic expiry.

❌ Pitfall: Flat Subscription Model

Putting everything in one subscription makes cost allocation and RBAC impossible. Use subscriptions as isolation boundaries, not just billing containers.

❌ Pitfall: Hub Network Without Firewall

A hub without Azure Firewall or NVA is just a transit network. You lose all visibility and control over east-west and north-south traffic.

Next Steps

This post covers the structural foundation. In future deep dives, I'll cover:

  • Subscription Vending Machine — Automated subscription provisioning
  • Private DNS at Scale — Centralized DNS for Private Endpoints
  • Cost Management Integration — FinOps from day one
  • Defender for Cloud Setup — Security posture management

Need Help Implementing This?

I help enterprises design and deploy production-ready Azure Landing Zones. From greenfield setups to brownfield migrations — let's build it right.

Book a Landing Zone Workshop

Tags

#Azure#LandingZones#Bicep#AzurePolicy#Infrastructureas Code#Technical

Need Help with Your Multi-Cloud Strategy?

I've helped Fortune 500 companies design and implement multi-cloud architectures that deliver real business value. Let's discuss how I can help your organization.

Book a Consultation

Not sure where to start?