Guidance on Importing existing Azure resources into terraform

mt1990 0 Reputation points
2026-10-14T15:35:58.05+00:00

Hi all,

I wanted to get some advice from the community.

I’ve recently been tasked with bringing our existing Azure estate under Terraform management. Historically, most of our Azure resources have been deployed manually through the Azure portal (click-ops).

I’ve been using the Azure CLI and aztfexport within VS Code to export our existing resource groups into Terraform, using the --hcl-only parameter.

We have 25+ resource groups containing a mixture of different resource types, so the exports have resulted in a significant amount of Terraform code.

I’m now looking for advice on the best strategy for refactoring this into a more manageable structure. Ideally, I’d like to separate resources into logical .tf files, for example:

  • compute.tf
  • networking.tf
  • storage.tf
  • recovery.tf
  • monitoring.tf

Once the code has been cleaned up and structured, my intention is to import the existing Azure resources into Terraform state and get to the point where terraform plan shows no changes before making any further changes through Terraform.

Has anyone gone through a similar brownfield Terraform adoption? I’d be interested to hear how you approached the refactoring and import process, particularly when dealing with a large number of existing resource groups and resources.

I’ve also had the suggestion of using Copilot to assist with some of the refactoring work. We have Microsoft 365 Copilot licensing as part of our E5 environment, however, I’m currently a little cautious about providing the full Terraform codebase directly to Copilot due to the amount of infrastructure and configuration information contained within it.

Has anyone used Copilot for this type of Terraform refactoring at scale? If so, I’d be interested to hear how you approached it

Azure DevOps
0 comments No comments

3 answers

Sort by: Most helpful
  1. Baraniwala Chamali Lalanthi Liyanage 0 Reputation points
    2026-10-14T18:49:10.3466667+00:00

    Hi mt1990,

    Taking on a brownfield Terraform adoption for 25+ resource groups is a massive undertaking, but you are definitely on the right track by using aztfexport with the --hcl-only flag first.

    Adding to Marcin's excellent advice, here are a few structural and security strategies you can use to make this process smoother and safer:

    1. Reassurances on Copilot Security (E5 Environment) You mentioned being cautious about feeding your infrastructure data to Microsoft 365 Copilot. Since you are on an E5 license, it is important to know that Microsoft 365 Copilot comes with Enterprise Data Protection (EDP). This means your prompts and the infrastructure data you paste are kept entirely within your Microsoft 365 tenant boundary. Your data is not retained, and it is not used to train any underlying foundation models.

    However, as Marcin pointed out, M365 Copilot is optimized for documents and emails. For refactoring HCL at scale, you should ideally use GitHub Copilot within VS Code. If your company has GitHub Copilot Business or Enterprise, it offers similar enterprise-grade data protection, meaning your codebase remains confidential.

    2. The "Module" Approach over Flat Files Separating resources into compute.tf, networking.tf, etc., is a good start, but doing this across 25+ resource groups in a flat directory will still become a maintenance nightmare.

    Recommendation: Group your state files by Lifecycle Environment (e.g., Prod, QA, Dev) or by Application/Workload, not just by Resource Group.

    Consider turning your repeated setups into Terraform Modules. Use Copilot to scan your exported HCL, identify repeating patterns (like standard VM setups or VNet configurations), and extract them into reusable modules.

    3. Leverage Terraform 1.5+ Import Blocks Since you want to reach a zero-change terraform plan, make heavy use of the new declarative import {} blocks introduced in Terraform 1.5. Instead of running state import commands manually in the CLI, you can define your imports directly in your .tf files. You can actually ask Copilot (even securely through the Edge browser logged in with your Entra ID) to generate these import {} blocks for you by feeding it a list of your Azure Resource IDs.

    4. Handle "Side-Effect" Resources

    When running your first terraform plan, expect to see drift on resources that Azure creates automatically (like default NSG rules, OS disks attached to VMs, or automatically generated storage accounts for boot diagnostics). Use lifecycle { ignore_changes = [tags, specific_attributes] } generously during this initial phase to achieve that clean plan, and then slowly remove them as you bring those attributes under Terraform's strict control.

    Good luck with the refactoring! Taking it one logical workload at a time is the safest way forward.

    Was this answer helpful?

    0 comments No comments

  2. Marcin Policht 110.5K Reputation points MVP Volunteer Moderator
    2026-10-14T18:42:49.9433333+00:00

    Tackling a brownfield Terraform adoption at this scale is indeed a major undertaking, but it should be achievable with a systematic approach. Since you used aztfexport with --hcl-only, you have the configuration code but no state file yet. To refactor this volume of code, you can leverage a combination of architectural isolation and targeted AI automation using GitHub Copilot. Microsoft 365 Copilot excels at workspace documents, but GitHub Copilot is designed for codebase comprehension and IDE integration, making it more suitable in this case.

    The core strategy is to limit the blast radius. Do not put all 25+ resource groups into a single Terraform state file. Doing so can increase plan execution times and make mistakes affect a much larger portion of your infrastructure. Instead, map one Terraform state or workspace to one Azure resource group, or to a small cluster of tightly coupled resource groups.

    For an individual resource group, aztfexport may output everything into a large main.tf file within a folder. Keep the directory structure intact for that resource group and create separate files in the same folder, such as networking.tf, compute.tf, storage.tf, recovery.tf, and monitoring.tf. Terraform evaluates all .tf files in a directory together, so moving resource blocks between files in the same directory does not change how Terraform tracks those resources.

    You can automate the refactoring process using GitHub Copilot inside Visual Studio Code instead of manually cutting and pasting thousands of lines across 25+ resource groups. Before scanning your code, verify that your organization's GitHub Copilot licensing and settings meet its security and compliance requirements. GitHub Copilot Business and Enterprise provide commercial data protections, but you should verify the applicable terms and settings rather than assume that all prompts and code are never logged or retained. With an individual license, review your GitHub account's data and privacy settings to control whether your interactions can be used for model training.

    Open the folder for a single resource group in VS Code, then use Copilot Edits or Copilot Chat to identify and reorganize resources. For example, ask Copilot to review main.tf, identify Azure networking resources such as azurerm_virtual_network, azurerm_subnet, azurerm_network_interface, and azurerm_public_ip, and move their complete resource blocks into networking.tf. Repeat this process for other resource categories, placing virtual machines, availability sets, and managed disks in compute.tf; storage accounts and container configurations in storage.tf; and Recovery Services vaults, Log Analytics resources, and diagnostic settings in recovery.tf and monitoring.tf, respectively. Review the resulting changes to ensure that no resource blocks, references, dependencies, or required configuration have been lost or altered.

    You can also use Copilot to help standardize the generated HCL. The output from aztfexport may contain repeated resource group names and location strings. Ask Copilot to review each file and identify opportunities to replace repeated values with references to standardized local values, such as local.resource_group_name and local.location. Ensure those local values are defined correctly and that the refactoring preserves the actual configuration of the existing Azure resources.

    Once the HCL files are organized, the next goal is to import the existing resources into Terraform state and reconcile the configuration with the deployed infrastructure. Terraform's native import blocks can simplify this process compared with executing hundreds of manual CLI commands. Each block maps an existing Azure resource ID to its corresponding Terraform resource address. For example, an import block can map an Azure virtual network resource ID to azurerm_virtual_network.example. Ensure that the resource block exists and that the address and resource ID are correct before running the import.

    Your first terraform plan may reveal configuration differences caused by provider defaults, computed attributes, dynamically generated IDs, or differences in how Azure and Terraform represent values, such as location casing. Review these differences. If Terraform proposes an unexpected modification or replacement, provide the relevant plan output to Copilot and ask it to identify the attribute causing the difference and suggest an appropriate HCL correction. Use lifecycle.ignore_changes only when the ignored attribute is intentionally managed outside Terraform and the resulting loss of drift detection is acceptable. Do not use it simply to suppress unexplained differences.

    The objective is to achieve a zero-change plan, meaning Terraform proposes no changes to the existing infrastructure after the resources have been imported and the configuration has been reconciled. Once the plan confirms that the configuration accurately represents the deployed resources, run terraform apply to complete any pending imports and finalize the state. Store the state in a securely configured remote backend with state locking and appropriate access controls. Note that importing resources and applying a zero-change plan does not itself lock the state permanently - the backend provides state storage and locking during Terraform operations.


    If the above response helps answer your question, remember to "Accept Answer" so that others in the community facing similar issues can easily find the solution. Your contribution is highly appreciated.

    hth

    Marcin

    Was this answer helpful?


  3. AI answer

    2026-10-14T15:36:23.3033333+00:00

    A practical brownfield approach is:

    1. Export in a way that preserves safety first.
      • aztfexport is the right tool for existing Azure resources that are not yet under Terraform management.
      • Since the current workflow uses --hcl-only, the generated HCL can be cleaned up before state import.
      • After refactoring, run terraform plan to confirm no drift before making changes.
    2. Refactor the generated code into logical files incrementally.
      • Splitting into compute.tf, networking.tf, storage.tf, recovery.tf, and monitoring.tf is a reasonable organization pattern.
      • Keep provider and Terraform blocks consistent while reorganizing files.
      • If exporting into a directory that already contains Terraform files, Azure Export for Terraform supports --append to add resources into an existing environment and avoids filename conflicts by generating files with a .aztfexport suffix.
    3. Handle unsupported or side-effect resources deliberately.
      • Some Azure resources have no corresponding Terraform resources.
      • Some resources are created as side effects of other resources, such as an OS disk created when provisioning a virtual machine.
      • In interactive mode, those resources can be skipped before writing configuration and importing state.
    4. Import state only after the configuration is in the desired structure.
      • The goal of getting to a clean terraform plan before future changes is aligned with the documented workflow: export HCL, inspect the resources, and integrate them into production infrastructure and remote backends.
      • For AzAPI-managed resources, existing resources can be brought under management with import blocks in Terraform 1.5+ or with terraform import. The resource ID must include the API version as a query parameter.
      • If many resources must be imported at once, aztfexport can generate both HCL and import blocks automatically.
    5. Use portal export selectively where it helps.
      • Azure portal export can generate Terraform configuration and state directly from a resource.
      • In the portal flow, the output can target either AzureRM or AzAPI, then the generated configuration and state can be downloaded and validated with terraform plan.
      • Portal export has limits: up to 1,000 resources and a maximum exported configuration payload of 4 MB, and some resource types or properties may not export.
    6. For large estates, work by scope.
      • aztfexport supports exporting a single resource, a resource group and nested resources, or an entire subscription.
      • For 25+ resource groups, a manageable pattern is to process one resource group or one logical scope at a time, refactor that scope, validate with terraform plan, then move to the next scope.
    7. Use the VS Code extension where it reduces manual effort.
      • The Microsoft Terraform VS Code extension supports Azure resource export via aztfexport and provides authoring help for AzureRM and AzAPI, including resource type discovery, property/value autocompletion, hover hints, syntax validation, and code samples.
      • That makes it useful during refactoring even when the initial export is already complete.

    For the Copilot part, the available material does not provide guidance on using Microsoft 365 Copilot to refactor Terraform codebases or on handling Terraform code confidentiality with Copilot. Based on the documented tooling, the supported path is to use aztfexport, the Azure portal export experience, and the Microsoft Terraform VS Code extension for export, migration, and authoring assistance.


    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.