The estate was built in the portal. Everyone involved knows it, nobody is proud of it, and the plan for fixing it has been "we'll do it properly on the next project" for three years. The next project keeps landing in the same estate.
What keeps it this way is an assumption about the price. In most heads, "move to infrastructure as code" means a rewrite: freeze delivery, rebuild everything from scratch as Terraform, and hope the new estate behaves like the old one when you swing traffic across. Priced like that the work never gets approved, and it shouldn't be. A big-bang infrastructure rewrite is a bad idea even when someone is brave enough to fund it.
The assumption is wrong, though. You don't have to rebuild anything. Terraform can take over resources that already exist, in place, with no downtime and no change to the running infrastructure. The migration isn't a move at all. It's a change of management: the resource stays exactly where it is, and what changes is who is allowed to modify it and how.
Adoption, not migration
Since Terraform 1.5, adoption is a first-class, reviewable operation. You declare what you're taking over in an import block:
import {
to = azurerm_storage_account.logs
id = "/subscriptions/<sub-id>/resourceGroups/rg-ops/providers/Microsoft.Storage/storageAccounts/stlogsprod"
}
And you can ask Terraform to draft the matching configuration for you:
terraform plan -generate-config-out=generated.tf
The acceptance test is beautifully strict. When the plan says 1 to import, 0 to add, 0 to change, 0 to destroy, the code is a faithful description of what's running. Nothing has been touched. You've gained a reviewable, versioned definition of a resource that used to exist only in the portal and in someone's memory, and from now on changes to it go through a pull request instead of a click.
That zero-diff plan is the answer to the changeover fear: there is no changeover. The old estate and the new estate are the same estate.
The order of attack
Adopting a whole estate at once is the rewrite wearing a different coat, so don't. Sequence it.
First, stop digging. From this week, new resources are born as code. This costs almost nothing on a new piece of work and it caps the size of the problem. An estate that stops growing by hand is already better off than it was.
Second, adopt the platform layer. Networking, DNS zones, policy assignments, shared identity. These are a small number of resources, they change rarely, and when someone does change one by hand the blast radius is the whole estate. Small count, high stakes: maximum return on the first import blocks you write.
Third, adopt what changes most often. Pull the resource groups with the busiest change history under management next, because every adopted resource converts its future changes from unreviewed clicks into reviewed pull requests. A resource that changes weekly pays you back every week. A resource nobody has touched since 2023 can wait.
What to deliberately leave alone
Not everything earns adoption, and knowing what to skip is half the judgment.
Anything scheduled for decommission stays out; writing code for a corpse is effort with no payback period. The same goes for vendor-managed appliances you're contractually not allowed to modify, and for resources another team genuinely owns, because adopting those turns a governance improvement into a turf war. Park them and note why.
The first fortnight
Week one: pick one resource group. Something real but calm, with a handful of resources. Import it, generate the config, tidy the generated HCL into something you'd let a colleague read, and get to the zero-diff plan.
Week two: make one real change to that resource group through code, reviewed and applied from a pipeline rather than a laptop. That single reviewed change is the proof the model works end to end, and it's the demo that wins over the sceptics: not a slide about IaC maturity, but a colleague watching a change ship with a review trail.
From there it's rhythm, not heroics. A resource group or two per sprint, platform layer first, busiest workloads next, delivery never pausing. Six months later the portal is read-only in your head long before you enforce it for real.
The import block and the zero-diff walkthrough are in the blog-examples repo.
More useful tidbits coming — one a week.