A well-architected Azure environment is crucial for any cloud adoption effort. This guide outlines a production-ready Azure Landing Zone implemented entirely as code, providing a secure, scalable, and automated foundation for your workloads. We'll leverage Terraform for Infrastructure as Code (IaC) and Azure DevOps for a robust Continuous Integration/Continuous Deployment (CI/CD) pipeline.
Github: https://github.com/dhanuka84/azure_landing_zone_project
✅ Azure Landing Zone Hierarchy
🔹 Billing and Identity
Billing Account → Single top-level EA or MCA. (an Enterprise Agreement (EA), which is a traditional, multi-year contract for large enterprises, or a Microsoft Customer Agreement (MCA), which is a more modern, flexible, and digital pay-as-you-go model)
Microsoft Entra ID Tenant → Hosts users, groups, and 3 app registrations:
app-cicd-dev, app-cicd-qa, app-cicd-prod.
🔹 Management Group Hierarchy
Tenant Root MG
│
├── Platform MG
│ └── Subscription: Connectivity
│ └── rg-platform-connectivity
│ ├── vnet-hub-weu
│ │ ├── AzureFirewallSubnet
│ │ ├── GatewaySubnet
│ │ └── AzureBastionSubnet
│ └── Private DNS Zones
│
├── Non-Production MG
│ ├── Subscription: Dev Environment
│ │ └── rg-dev-user-a
│ │ └── vnet-dev-spoke (peered with hub)
│ └── Subscription: QA Environment
│ └── rg-qa-app-services
│ └── vnet-qa-spoke (peered with hub)
│
└── Production MG
└── Subscription: Prod Environment
└── rg-prod-app-services
├── vnet-prod-spoke (peered with hub)
├── aks-prod-main
├── acr-prod-main
└── kv-prod-secrets
🔹 Networking Model
Hub VNet (vnet-hub-weu) in Connectivity subscription.
Spoke VNets (vnet-dev-spoke, vnet-qa-spoke, vnet-prod-spoke) in each environment subscription.
Peering from each spoke to Hub.
UDR in spokes forcing outbound traffic via Azure Firewall in Hub.
Private DNS zones hosted centrally in Hub.
🔹 CI/CD (Azure DevOps)
Uses 3 service connections:
azrm-platform → Connectivity subscription
azrm-nonprod → Dev + QA subscriptions
azrm-prod → Production subscription
Stages: platform_mg → platform_connectivity → env_dev → env_qa → env_prod
🧭 1. The Mental Model: What We’re Building
Governance
Management Groups
Platform → Non-Production → Production
Apply policies, logging, and guardrails at each level.
Networking
https://dhanuka84.blogspot.com/p/azure-landing-zone.html
Hub-and-Spoke Topology
Hub VNet (vnet-hub-weu) → Shared services:
AzureFirewallSubnet with Azure Firewall
GatewaySubnet reserved for VPN/ExpressRoute Gateway
AzureBastionSubnet for secure RDP/SSH
Private DNS Zones for Key Vault, ACR etc.
Spoke VNets (vnet-dev-spoke, vnet-qa-spoke, vnet-prod-spoke) peer back to the hub.
UDRs route 0.0.0.0/0 through the firewall for outbound inspection.
Subscriptions
Connectivity → Hub + shared infra
Dev, QA, Prod → isolated spokes
🧱 2. Identity and Security – Microsoft Entra ID
https://dhanuka84.blogspot.com/p/azure-entra-id.html
Terraform assigns RBAC roles declaratively:
module "rbac" {
assignments = [
{
scope_id = module.acr.id
role_definition = "AcrPush"
principal_objectId = var.spn_app_cicd_prod
},
{
scope_id = module.kv.id
role_definition = "Key Vault Secrets User"
principal_objectId = var.spn_key_vault_api_prod
}
]
}
🧩 3. The Project Layout
infra/
├─ modules/ # Reusable IaC building blocks
│ ├─ networking-hub/
│ ├─ azure-firewall/
│ ├─ udr/
│ ├─ networking-spoke/
│ ├─ aks/ acr/ keyvault/ private-endpoint/ rbac/
│
├─ platform/
│ ├─ mg/ # Management Groups & Policy
│ └─ connectivity/ # Hub + Firewall + DNS
│
├─ envs/
│ ├─ dev/ qa/ prod/ # Spoke VNets, AKS, etc.
│
└─ pipelines/
└─ azure-pipelines.yml # CI/CD definition
⚙️ 4. Step-by-Step Deployment
Step 1 – Remote State
az group create -n rg-tfstate -l westeurope
az storage account create -n saterraformstate123 -g rg-tfstate \
--sku Standard_LRS --encryption-services blob
az storage container create -n tfstate --account-name saterraformstate123
Record these for the variable group.
Step 2 – Azure DevOps Setup
Service Connections
azrm-platform → Connectivity subscription
azrm-nonprod → Dev + QA
azrm-prod → Production
Variable Group vg-terraform
TF_STATE_RG=rg-tfstate
TF_STATE_SA=saterraformstate123
TF_STATE_CONTAINER=tfstate
TF_STATE_KEY_PLATFORM_MG=platform_mg.tfstate
TF_STATE_KEY_CONNECTIVITY=connectivity.tfstate
TF_STATE_KEY_DEV=dev.tfstate
TF_STATE_KEY_QA=qa.tfstate
TF_STATE_KEY_PROD=prod.tfstate
Step 3 – Run the Pipeline
In Pipelines → New Pipeline
Choose existing YAML → /infra/pipelines/azure-pipelines.yml
Stages executed:
🌐 5. Inspecting the Hub
After the pipeline:
rg-platform-connectivity
├─ vnet-hub-weu
│ ├─ AzureFirewallSubnet → Azure Firewall
│ ├─ GatewaySubnet → (optional) VPN/ER Gateway
│ └─ AzureBastionSubnet → Bastion
└─ Private DNS Zones
Tip: you can extend the hub with:
resource "azurerm_virtual_network_gateway" "vpngw" { … }
to enable hybrid connectivity via the existing GatewaySubnet.
🧠 6. Environment Spokes
Each environment’s main file composes modules.
For example, Prod creates:
Spoke VNet + subnets
AKS cluster (aks-prod-main)
ACR (acrprodmain)
Key Vault (kv-prod-secrets)
Private Endpoints
UDR routing → Firewall
✅ 7. Validate Deployment
az network vnet list -g rg-platform-connectivity -o table
az network firewall show -n afw-hub -g rg-platform-connectivity
az aks get-credentials -n aks-prod-main -g rg-prod-app-services
Confirm traffic between spokes routes through the firewall (check UDRs).
🧱 8. Extending the Landing Zone
Add Azure Policy Assignments to enforce tagging, TLS, logging.
Add Log Analytics workspace and diagnostics for Firewall/AKS.
Implement Private DNS zone links for name resolution across spokes.
Enable ExpressRoute Gateway using the existing GatewaySubnet.
🏁 Conclusion
You now have a production-ready Azure Landing Zone implemented entirely as code:
Governance with Management Groups
Secure hub with Firewall + UDRs
Isolated spokes for Dev/QA/Prod
Automated CI/CD via Azure DevOps
Identity and RBAC managed by Entra ID
This foundation scales as you onboard new workloads—just add new spokes or modules without breaking compliance or security boundaries.

.jpg)