Last Updated on May 29, 2026 by Arnav Sharma
Most Azure environments do not start with Terraform. They start with the portal, with ARM templates, with ad hoc deployments across teams who were moving fast. At some point, the decision gets made to bring everything under Infrastructure as Code, and then the real work begins: how do you get hundreds of existing Azure resources into Terraform state without rebuilding them from scratch?
That is exactly what Azure Export for Terraform (aztfexport) solves. This guide covers how the tool actually works, where it falls short, how to use it in an Azure environment, and how to produce clean, production-ready Terraform from the output it generates.
What Is aztfexport and How Did It Get Here
Azure Export for Terraform was originally released by Microsoft as aztfy in 2022. The tool was renamed to aztfexportas it matured, and it remains actively maintained on GitHub under the Azure organisation. The name change caused some confusion in the community and a lot of outdated documentation, but the tool itself is stable and production-capable.
The core problem aztfexport solves is the bootstrapping problem in Terraform adoption. Terraform’s native terraform import command imports a single resource at a time into state, but does not generate the corresponding HCL configuration. You still have to write the .tf files yourself. For a resource group with 200 resources, that is a significant amount of work before you even run terraform plan.
aztfexport automates the full cycle: it discovers resources in a target scope, runs terraform import for each one, then uses a companion tool called tfadd to generate the HCL configuration from the imported state. The result is a set of .tf files that you can start managing immediately with Terraform.
How aztfexport Works Under the Hood
Understanding the internals helps you predict where the tool will produce clean output and where it will need manual work.
aztfexport relies on three components working together:
aztft identifies the correct AzureRM Terraform provider resource type for a given Azure resource ID. This is harder than it sounds because there is not always a 1:1 mapping between ARM resource types and Terraform resource types. aztft maintains a mapping database that covers the most common AzureRM resources.
terraform import does the actual state import for each identified resource, using the standard Terraform import mechanism. This means the import respects your configured backend, whether that is local state, Azure Storage, or Terraform Cloud.
tfadd generates HCL configuration from the imported Terraform state. It reads what is now in state and writes the corresponding resource blocks into .tf files.
The limitation this architecture creates is worth understanding clearly: the generated Terraform configurations are not meant to be comprehensive and do not guarantee that the infrastructure can be fully reproduced from those configurations. Properties with cross-property constraints in the AzureRM provider are sometimes omitted from generated code to avoid validation errors. This means you will nearly always need to review and refine the output before treating it as production-ready IaC.
Installation
aztfexport is distributed as a precompiled binary for Windows, macOS, and Linux. The installation process is straightforward.
macOS (Homebrew):
brew install aztfexport
Ubuntu/Debian:
curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo apt-add-repository https://packages.microsoft.com/ubuntu/20.04/prod
sudo apt-get install aztfexport
Windows (winget):
winget install aztfexport
Before running aztfexport, you need:
- Azure CLI installed and authenticated (
az login) - Terraform v1.0 or higher in your PATH
- The correct subscription set as default (
az account set --subscription <ID>)
Verify the installation with:
aztfexport version
Import Modes: Choosing the Right Scope
aztfexport supports four import modes. Choosing the right one saves significant cleanup time.
Resource Group Import
The most common starting point. Imports all resources within a specified resource group:
aztfexport resource-group --output-dir ./tf-output MyResourceGroup
This is the right mode when you want to bring an entire logical workload under Terraform management. The generated output directory contains a main.tf with all discovered resources and a Terraform state file.
Single Resource Import
Targets a specific Azure resource by its ARM resource ID:
aztfexport resource /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Network/virtualNetworks/my-vnet
Useful when you want to bring in individual resources rather than entire resource groups. More predictable output, less cleanup required.
Subscription Import
Imports all resources across an entire subscription:
aztfexport subscription --subscription-id <sub-id> --output-dir ./tf-output
Use this with caution. Subscription-wide imports on large tenants can take hours and produce very large amounts of output requiring extensive cleanup. For most organisations, resource-group-level imports are more practical.
Query-Based Import
Imports resources matching an Azure Resource Graph query:
aztfexport query --predicate "type == 'microsoft.network/virtualnetworks'" --output-dir ./tf-output
This mode is particularly useful for organisations that want to import resources by type across multiple resource groups, for example, all Key Vaults or all storage accounts in a subscription.
Running an Import: What to Expect
When you run a resource group import, aztfexport presents an interactive terminal UI that lists all discovered resources. For each resource, you will see:
- The ARM resource type
- The detected Terraform resource type (where aztft has a mapping)
- A status indicator showing whether the resource can be auto-imported
Resources marked with a light bulb icon have a known Terraform mapping and will be imported automatically. Resources without a known mapping require you to either specify the Terraform resource type manually or skip them.
There are two ways to run the import:
Interactive mode (default): Review each resource before importing. Good for initial exploration.
Non-interactive mode: Run with --non-interactive to process all recognisable resources automatically. Unrecognised resources are skipped. Better for scripted or repeatable use.
aztfexport resource-group --non-interactive --output-dir ./tf-output MyResourceGroup
Known Limitations You Will Encounter
No tool that attempts to reverse-engineer IaC from deployed infrastructure is perfect. aztfexport has specific limitations that every practitioner running it against a real Azure environment will hit:
Property-like resources are skipped. Some Terraform resources model properties of their parent rather than standalone Azure resources. For example, azurerm_subnet can be managed as its own resource or as a nested block within azurerm_virtual_network. aztfexport may import the VNet but skip the subnets, requiring manual addition.
Deprecated AzureRM resource types appear in output. The AzureRM provider has evolved significantly, and aztfexport sometimes generates resource blocks using older resource types that have been superseded. For example, azurerm_app_service instead of azurerm_linux_web_app. These resources still work but will produce deprecation warnings on terraform plan and will eventually stop working when the deprecated types are removed from the provider. Plan a review pass specifically for deprecated resource types after every import.
Hardcoded values require parameterisation. Generated code contains hardcoded resource IDs, subscription IDs, location names, and other values that should be variables in a well-structured Terraform codebase. The import gets you into state; the cleanup work gets you to production-quality code.
Cross-resource dependencies are expressed with depends_on blocks rather than resource references. This is functional but not idiomatic Terraform and creates a more brittle dependency graph than explicit references would.
Post-Import Cleanup: Getting to Production-Ready Terraform
The import itself is the first 20% of the work. The remaining 80% is making the generated code something your team can actually maintain. The following sequence works well in practice.
Step 1: Run terraform plan immediately
Before making any changes, run terraform plan on the imported output. A clean import should produce no planned changes: Terraform’s understanding of the resources should match their actual deployed state. If you see planned changes, that indicates either a mapping gap in aztfexport or a resource property the tool could not fully capture. Investigate and resolve these before proceeding.
Step 2: Identify and fix deprecated resource types
Search the generated code for resource types that are no longer current in the AzureRM provider. Common ones include azurerm_app_service, azurerm_app_service_plan, and azurerm_kubernetes_cluster properties that have moved to new arguments. The AzureRM provider changelog is the reference for what has been deprecated in your provider version.
Step 3: Extract variables
Move hardcoded values into a variables.tf file. At minimum, extract:
- Subscription ID
- Location
- Resource group names referenced by other resources
- Any values that differ between environments (sizes, SKUs, counts)
Step 4: Replace depends_on with resource references
Where aztfexport has generated depends_on blocks, evaluate whether you can replace them with explicit resource attribute references. For example, replacing a depends_on = [azurerm_subnet.main] with subnet_id = azurerm_subnet.main.id is both cleaner and more accurate.
Step 5: Split into modules where appropriate
The generated output places all resources in a single main.tf. For anything beyond a small resource group, breaking this into logical modules (networking, compute, security) makes the code significantly more maintainable.
aztfexport vs Native terraform import
Terraform 1.5 introduced import blocks, which allow you to declare imports declaratively in HCL and have them executed as part of a terraform plan and terraform apply cycle. Terraform 1.6 added terraform plan -generate-config-out which can generate HCL configuration for imported resources, similar to what aztfexport does.
The native approach is cleaner for single resources or small numbers of resources. aztfexport remains the better choice for large-scale bulk imports, particularly of entire resource groups, because:
- It handles resource discovery automatically rather than requiring you to know every resource ID in advance
- The aztft mapping library covers a broader range of resource types than the native config generation in its current form
- The interactive UI makes it practical to work through a large resource group and handle edge cases on the fly
For organisations actively building new infrastructure, native import blocks are the right choice. For the one-time work of bringing an existing Azure estate under Terraform management, aztfexport is the better tool.
A Practical Workflow for Australian Organisations
Australian organisations operating under the ISM or Essential Eight have specific requirements around change management, audit trails, and state file security that affect how you structure an aztfexport-based migration.
State file security. The Terraform state file generated by aztfexport contains sensitive information including resource IDs, connection strings, and configuration details. Before treating this state file as your production state, migrate it to a remote backend in Azure Storage with encryption at rest, storage account versioning and soft delete enabled, and managed identity authentication. Do not commit state files to source control.
Change tracking. The ISM requires documentation of configuration changes to systems within scope. Treating the aztfexport migration as a formal change, with the generated Terraform code committed to a Git repository as the starting point, provides the audit trail needed for compliance. Each subsequent infrastructure change then flows through the standard IaC pipeline with pull request review and approval.
Environment separation. Import into a non-production equivalent first where possible. Running aztfexport against a production resource group that contains critical infrastructure, then discovering that terraform plan shows unexpected planned changes, is a stressful situation. Test the import and cleanup process on a staging environment before running it against production.
Scope limitation for PROTECTED workloads. For resources handling data classified at PROTECTED under the ISM, verify that the import process itself does not expose sensitive configuration values through temporary local state files. Use --backend-type azurerm with aztfexport to write directly to a remote backend rather than storing state locally during the import process.
What aztfexport Will Not Solve
aztfexport gets your existing infrastructure into Terraform state and generates a starting point for your configuration. It does not:
- Enforce naming conventions or resource hierarchy standards
- Structure your code into maintainable modules
- Resolve architectural issues in your existing infrastructure
- Handle resources that have no AzureRM Terraform equivalent
For organisations that have significant technical debt in their Azure environment, a Terraform migration is an opportunity to also address architectural issues rather than simply importing existing problems into IaC. The import gives you visibility and control; what you do with that control is still an architectural decision.
I help organisations secure their cloud infrastructure and stay ahead of evolving cyber threats. Microsoft MVP and Certified Trainer, author of Mastering Azure Security, and founder of arnav.au — a platform for practical Cloud, Cybersecurity, DevOps and AI content.
Frequently Asked Questions
Azure Terrafy is a tool that links existing Azure infrastructure with Terraform, allowing you to import and manage your current Azure resources as code. It bridges the gap between Azure infrastructure and Terraform, enabling you to bring existing infrastructure under control by codifying it and managing it through version control.
To import existing Azure resources, use the 'aztfy' Terraform module to generate your Terraform configuration files. Once generated, use the 'import' command to import your existing resources into your Terraform state file, after which you can manage them like any other Terraform-defined resources.
First, install the Azure CLI and authenticate with your Azure credentials. Then run the 'aztfy init' command to initialize a new Terraform module in your chosen directory. After initialization, you can start working with Azure resources in your Terraform configuration files using commands like 'terraform plan' and 'terraform apply'.
Terraform state is critical because it tracks the status of your deployed infrastructure and all its resources. State files track changes and write them to disk, allowing you to manage your infrastructure efficiently, control updates, and maintain version control across your environment.
You need to add the 'azurerm' provider to your Terraform configuration files to work with Azure Resource Management. Once installed, you can use various resource types provided by Terraform such as load balancers, virtual networks, and security groups to manage your Azure infrastructure.