Terraform for Azure Landing Zones: What Two New Zealand Migrations Taught Us
Practical landing zone lessons from live migration programmes: who owns the VNet, per-workload state, naming as a contract, workload identity instead of stored secrets, and why your Terraform should inhabit the landing zone rather than create it.
Terraform for Azure Landing Zones: What Two New Zealand Migrations Taught Us
“Landing zone” is one of those Azure terms that means everything and therefore nothing. Microsoft’s Cloud Adoption Framework gives you the reference architecture; what it cannot give you is the set of decisions that make Terraform work inside one on a real programme, with a real platform team, under a real budget.
D2 Solutions is currently working inside two New Zealand landing zone programmes: an Azure tenant-to-tenant migration for the Electricity Authority, the Crown entity that governs New Zealand’s electricity market, and a seven-system legacy migration for Partners Life, one of the country’s largest life insurers. Different clients, different estates, same hard-won lessons. Here are the ones that transfer.
1. Decide who owns the VNet before you write a single module
The single most productive boundary we work with: the platform team owns the landing zone, the delivery team inhabits it. Subscriptions, VNets, DNS, peering, policy - provided. Application infrastructure - ours, deployed into what is provided.
That means our Terraform references the VNet as a data source; it never creates one. The moment application IaC starts creating network topology, you get drift between what the platform team believes exists and what the last terraform apply decided, and every incident review starts with an argument about whose diagram is true.
If you inherit legacy Terraform, audit it for exactly this. Estates that grew organically almost always have application modules quietly creating networking, monitoring workspaces, or shared resources that the target landing zone already provides. Deleting those resources from code is not lost work; it is the migration.
2. Per-workload everything: subscription, VNet, state
The strongest structural pattern in both programmes: each workload gets its own subscription, its own VNet, and its own Terraform state.
- Blast radius: a bad apply touches one workload, not the estate.
- Cost attribution: the subscription is the invoice line. No tagging archaeology.
- State hygiene: no mega-state that takes twelve minutes to plan and locks the whole team out during a deploy. State splits along the same boundary as everything else.
- Access: RBAC scopes cleanly at the subscription edge; service connections hold Contributor on exactly one workload’s resources.
The objection is always overhead: “dozens of subscriptions?” Yes, and it is cheaper than one shared subscription where four teams negotiate quota, policy exceptions, and each other’s mistakes.
3. Naming is a contract, not a style preference
Both programmes enforce a strict convention carrying environment, region, and workload in every resource name (the shape of pl-{env}-{region}-{workload}-{type}). When the convention is the contract, half-matching it is worse than not matching it: every consumer downstream (policy, monitoring queries, cost reports, humans on call at 3 AM) keys off the names.
The practical consequence for migration work: legacy locals.tf naming logic gets rewritten, not adapted. Trying to bend an old naming scheme toward a new convention with string surgery produces resources that match neither, and you only find out when a policy denies the deploy or an alert routes nowhere.
4. Workload identity federation ends the secret lifecycle
Estates built over a decade authenticate the way their era did: service principal secrets in tfvars, connection strings in pipeline variables. On the target side we draw a hard line:
- Pipelines authenticate to Azure with workload identity federation on the service connection. No stored secret, so nothing to expire, rotate, or find in an old branch three years later.
- Runtime access uses managed identity plus RBAC - SQL, Key Vault, storage - instead of keys.
- Anything that ever lived in a repository gets rotated on the way out, as policy, regardless of whether anyone believes it leaked. Git history is forever.
On the Electricity Authority programme this went as far as sign-in flows that require no client secret at all: app registrations in the target tenant holding zero credentials. That is what a regulator’s estate should look like, and Terraform is where you make it the default rather than the exception.
5. Hunt hardcoded values before you plan the migration
Every estate has them: a Key Vault endpoint compiled into a shared library, a storage account name in a template, a subscription ID in a tfvars file, alert emails pointing at a vendor that left two years ago. Each one is invisible until the day the resource it points at is in the wrong tenant.
Inventory the literals first. On both programmes, the true critical path was never the infrastructure work; it was a hardcoded value in shared code that no app setting could override. Finding those in week one instead of week nine is the difference between a migration plan and a migration surprise.
6. Price the architecture honestly, before you build it
Landing zone requirements change runtime economics. The sharpest example we hit: a Consumption Logic App that had to reach storage which was no longer allowed to be publicly reachable. Consumption Logic Apps do not integrate into VNets; the clean answer is Logic App Standard, which is single-tenant, VNet-integrated, and a new fixed monthly cost against Consumption’s pay-per-execution, plus real re-authoring work.
The lesson is not “use Standard”. The lesson is: when the landing zone imposes private networking, put the cost delta and the conversion effort on the table with numbers before anyone commits. On a tight budget, surprise architecture is worse than expensive architecture.
The transferable playbook
- Platform team owns the zone; your Terraform inhabits it, referencing networks as data sources.
- Per-workload subscription, VNet, and state - isolation along one consistent boundary.
- Naming convention as a contract; rewrite legacy naming logic, do not adapt it.
- Workload identity federation for pipelines, managed identity for runtime, rotate everything that ever touched a repo.
- Inventory hardcoded values before planning; they are your critical path.
- Price VNet-driven architecture changes before building them.
None of this is exotic. All of it is the difference between a landing zone that looks like the reference architecture and one that operates like it. If you are planning an Azure landing zone build-out or a migration into one, this is the work D2 Solutions does - talk to us at [email protected].
Platform, SRE, and AI infrastructure for production systems. Building Horizon, the AI engineering operations platform now running in production across 7 Kubernetes clusters and 10+ tenants.