Home / Azure

AzureHow-toIntermediate

Transformation: bring portal-built Azure resources under Infrastructure as Code (Bicep or Terraform)

Move an Azure estate built by hand in the portal to Infrastructure as Code without recreating anything. Export and clean templates, import existing resources into Terraform state, detect drift and lock the portal down step by step.

Deepak Bhardwaj2 min read

Applies toAzure Resource Manager, Bicep CLI 0.20+, Terraform 1.5+ with the azurerm provider, Azure CLI 2.50+

The goal

Every production change goes through a pull request and a pipeline, and the code describes what really exists. You get there by adopting what is already deployed, not by rebuilding it.

1. Pick a tool and a scope

Choose When
Bicep Azure-only estate, team knows ARM, want day-zero support for new resource types
Terraform Multi-cloud or existing Terraform skills, want plan output and a large module ecosystem

Start with one landing zone or one application resource group, not the whole tenant.

2. Bicep path: export, decompile, clean

az group export -g rg-app-prod > rg-app-prod.json
az bicep decompile --file rg-app-prod.json

The output works but is noisy. Clean it:

  • Replace hard-coded names, IDs and locations with parameters.
  • Remove read-only properties and defaults.
  • Split into modules (network, data, compute).

Check with what-if — the goal is no changes:

az deployment group what-if -g rg-app-prod -f main.bicep -p @prod.bicepparam

Iterate until what-if shows nothing to modify (or only harmless noise you understand).

3. Terraform path: import blocks

Terraform 1.5+ can generate configuration for existing resources:

import {
  to = azurerm_resource_group.app
  id = "/subscriptions/<sub>/resourceGroups/rg-app-prod"
}

import {
  to = azurerm_storage_account.data
  id = "/subscriptions/<sub>/resourceGroups/rg-app-prod/providers/Microsoft.Storage/storageAccounts/stappprod"
}
terraform plan -generate-config-out=generated.tf
terraform apply      # imports into state; should report 0 to add, 0 to change, 0 to destroy

Refactor generated.tf into modules and variables, then re-run terraform plan until it is clean. Microsoft's aztfexport tool can also bulk-export a resource group to Terraform.

Caution

Never apply a plan that destroys or replaces production resources during adoption. A "forces replacement" line means your code differs from reality on an immutable property — fix the code.

4. Pipeline and state

  • Store Terraform state in a locked-down storage account with versioning and soft delete.
  • Run plan on pull request, apply on merge, with an approval gate for production.
  • Use workload identity federation for the pipeline identity, not a client secret.

5. Stop the drift

  1. Scheduled pipeline runs what-if / terraform plan nightly and alerts on any difference.
  2. Reduce portal write access: move people from Contributor to Reader on production, keep a break-glass group.
  3. Use Azure Policy to deny resources without required tags that only the pipeline sets.

Checklist

  • One scope adopted end to end, with no-change plan
  • Remote state secured and backed up
  • PR-based pipeline with approvals
  • Nightly drift detection
  • Portal write access reduced