Salt la conținutul principal
Cloud Architecture15 min read

Azure Landing Zones: Technical Deep Dive for Architects

Bicep code, policy definitions, and implementation patterns

IM

Iulian Mihai

Arhitect Principal Cloud și Lider în Inovație AI

Azure Landing Zones Technical Architecture with Bicep Code
🎧 Listen to this article
Azure Landing Zones: Technical Deep Dive for Architects
7:29
0:007:29

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'
      }
    }
  }
}

💡Need help with your Bicep/Terraform setup?

I can review your current infrastructure code or help you build production-ready modules.

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
🎓

Want to Go Deeper?

1 DayArchitects & Engineers

Azure Landing Zone Readiness Workshop

A hands-on 1-day workshop where we design your management group hierarchy, deploy policies with Bicep, configure hub-spoke networking, and build your implementation roadmap.

🌍 Remote (Global)🇪🇺 On-site (Spain, EU)🔄 Hybrid Available

Etichete

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

Need Expert Cloud Architecture Guidance?

I've helped Fortune 500 companies design secure, scalable cloud architectures that deliver real business value — Landing Zones, multi-cloud governance, and migration strategy. Let's discuss your challenges.

Nu știi de unde să începi?