Growing the tree¶
The template ships platform/ and landingzones/ as README-only placeholder
folders. This page explains what each placeholder becomes, and how you fill one in
when a customer needs a connectivity hub, a landing zone, or another unit.
The placeholders are deliberate¶
A folder with no terragrunt.hcl is not a unit. Discovery walks straight past it, so
a README-only folder costs nothing at plan time and nothing at apply time. The tree
documents the shape of the estate before the resources exist.
That is why every management group in the Cloud Adoption Framework hierarchy has a folder from day one, even the ones with no subscription. You read the tree and see where things will go. When a subscription arrives, you add files to a folder that is already in the right place, and the state key follows the path automatically.
What each placeholder is for¶
Each README states the management group it represents and what belongs there.
| Folder | Management group | What it becomes |
|---|---|---|
platform/connectivity/ |
connectivity |
The hub network. Onboarded per customer. |
platform/identity/ |
identity |
Identity services. No subscription in v1. |
platform/management/ |
management |
Management-subscription workloads, for example a backup vault. |
platform/security/ |
security |
Security tooling. No subscription in v1. |
landingzones/corp/ |
corp |
Corp-connected workloads, no public ingress by default. |
landingzones/online/ |
online |
Internet-facing workloads. |
landingzones/local/ |
local |
Azure Local landing zones. Carries the local archetype and its single Enforce-ALDO-Services assignment. Most customers never use it, but the management group is still created, because the ALZ module needs a complete architecture. |
sandbox/ |
sandbox |
Loosely governed experimentation subscriptions. |
decommissioned/ |
decommissioned |
Holding area for subscriptions being retired. |
platform/management/ does not hold the tenant foundation. The management group
hierarchy, the base policy, the Log Analytics workspace, the data collection rules,
and the AMA identity all live in live/_foundation/, deployed once at the top of the
tree. The landing-zones unit's policy assignments reference the management resources
by id and need them to exist first. See The foundation units.
The flattening rule¶
A platform management group with exactly one subscription puts subscription.hcl
directly in the management group folder. There is no named subscription level, because
there is nothing to name it apart from.
live/platform/connectivity/
├── subscription.hcl # the connectivity subscription
└── westeurope/
├── region.hcl
└── caf-connectivity-hub/
A landing zone uses a named subscription folder, because a management group holds many of them.
live/landingzones/corp/
└── corp-workload/ # the subscription, named for its purpose
├── subscription.hcl
├── _global/
└── westeurope/
Onboarding a connectivity hub¶
Three things go in: a subscription.hcl with the connectivity subscription id, a
region folder with its region.hcl, and a hub unit under that region folder.
The hub deploys into the connectivity subscription, which is a different subscription
from the foundation's management subscription. Its subscription.hcl sets that id
explicitly. State still goes to the one shared backend in the management subscription,
over Entra ID, because root.hcl reads the backend subscription separately from the
per-folder subscription.hcl.
Start from the minimal, low-cost hub: one virtual network, with the firewall, firewall policy, bastion, both gateways, private DNS zones, private DNS resolver, NAT gateway, and DDoS plan all off. Turn an option on per hub when a workload needs it.
In nrit-alz-live, live/platform/connectivity/westeurope/caf-connectivity-hub/
sources two modules:
| Module | Version | Creates |
|---|---|---|
Azure/avm-res-resources-resourcegroup/azurerm |
0.4.0 |
rg-hub-weu. |
Azure/avm-ptn-alz-connectivity-hub-and-spoke-vnet/azurerm |
0.17.1 |
vnet-hub-weu, 10.0.0.0/16. |
The hub module defaults every optional resource to on and gates creation on the
flag alone, so each one is set false explicitly in main.tf. The DDoS plan is turned
off in hub_and_spoke_networks_settings instead.
Private DNS zones need a foundation edit
If you enable private_dns_zones on the hub, also wire the foundation policy
default values in live/_foundation/landing-zones/main.tf, so the ALZ policy
assignments reference the live resources rather than nothing.
Onboarding a landing zone¶
A landing zone is two units in one named subscription folder, and they own different things on purpose.
_global/lz-vending/owns the subscription lifecycle only: alias or adoption, placement under the target management group, identities, RBAC, and budgets. It creates no network. It is region-agnostic, so it sits under_global/with a co-locatedregion.hclthat exists only becauseroot.hclreads one from every unit.<region>/network/owns all spoke network config: the resource group, the spoke virtual network, subnets, NSGs, route tables, and hub peering.
Apply order is the connectivity hub, then lz-vending, then the spoke network. Corp
versus online is just the target management group set in lz-vending.
In nrit-alz-live, live/landingzones/corp/corp-workload/ uses the same module for
both units, configured differently:
| Unit | Module | Version | Configured for |
|---|---|---|---|
_global/lz-vending/ |
Azure/avm-ptn-alz-sub-vending/azure |
0.2.1 |
Adopt the existing subscription (subscription_alias_enabled = false), place it under corp. No UMI, RBAC, or budget. |
westeurope/network/ |
Azure/avm-ptn-alz-sub-vending/azure |
0.2.1 |
Network only. Every subscription-level input fixed off so the two units never contend for subscription-level state. |
The spoke peers to the hub by a resource id computed from the connectivity subscription, resource group, and VNet name. That keeps it known at plan time and avoids reading another unit's output.
Two settings you will get wrong once:
- Every subnet needs a
network_security_groupsentry associated to it. The ALZ landing-zone policyDeny-Subnet-Without-Nsgdenies any subnet with none. - Set
hub_peering_options_tohub = { use_remote_gateways = false }when the hub has no VPN or ExpressRoute gateway. The module default istrue, and the peering fails against a gateway-less hub.
Adding a unit¶
The same four steps whatever the unit deploys.
- Create the leaf folder under the right scope, so it inherits the subscription and region you want.
- Add a
terragrunt.hclthat includesroot.hcland declares any ordering in adependenciesblock. - Add a
main.tfthat sources an Azure Verified Module at a pinnedversionand sets its inputs. - Open a pull request. Discovery picks the new unit up, plans it, and posts the result on the PR.
Adding a region¶
Add the region folder with its region.hcl, then the units under it. What multiplies
per region is connectivity (a hub per region) and the workload spokes.
You do not touch _foundation/. The foundation is region-independent: the management
groups and policy are global, and location on the two policy modules only picks the
region for their managed identities. The one real regional decision is the Log
Analytics workspace, and the template keeps a single central workspace, which is the
ALZ single-pane default. AMBA alerts still land in the new region, because the AMBA
policy's deployIfNotExists deploys them wherever the target resources are.
Move the workspace to a per-region folder under
platform/management/<region>/management-resources/ only when data residency requires
it. Governance stays global either way. The template's docs/foundation-structure.md
has the full reasoning and the cross-tree references that shape costs.
Adding a workload resource¶
A common single resource (a storage account, a key vault, a Log Analytics workspace, a user-assigned identity) goes in its own unit under a region folder, next to the network unit. Each gets its own state, so it plans and applies without touching the spoke.
nrit-alz-live keeps a minimal example of this shape at
live/landingzones/corp/corp-workload/westeurope/example/: a terragrunt.hcl that
includes the root and nothing else, and a main.tf sourcing Azure/naming/azurerm
0.4.3 and Azure/avm-res-resources-resourcegroup/azurerm 0.4.0.
Next: Runbook.