AnalysisMay 2026

Why Azure's Resource Group Export template falls short of usable IaC

An analysis of why Azure's native export tools for ARM, Bicep, and Terraform often produce messy, unmaintainable code despite their conceptually clean underlying mechanics.

Azure ships a built-in Export template feature on every resource group. The Bicep and ARM exporters sit on top of the same machinery: they walk the resource graph of a resource group, issue GET calls per resource through the ARM API, and translate each response into a template in the target language.

For Terraform AzureRM, the path is different. Microsoft maintains a separate open-source tool called aztfexport (formerly aztfy), which doesn't go through the ARM template export pipeline. Instead, it discovers resources via Azure Resource Manager and Azure Resource Graph, then maps each Azure resource ID to a Terraform resource type using a helper library (aztft). From there, it runs terraform import to bring the resource into state, and uses another helper (tfadd) to generate HCL from that state. So the Terraform output is shaped less by the ARM API directly and more by how the AzureRM provider's import logic handles each resource.

Conceptually clean. In practice, the output is rarely something you'd want to deploy as-is.

I've spent a fair amount of time running this end-to-end across all three target languages: ARM, Bicep, and Terraform. Honestly, I wouldn't recommend leaning on the native export for anything you plan to actually maintain. But if you're going to use it anyway, these are the things worth knowing going in:

1. GET returns runtime state, not deployment configuration

When you GET a resource, you don't get back the configuration that deployed it, you get the current state of the resource. That includes every read-only property the platform populated during creation. None of that belongs in your IaC. They're outputs, not inputs. Depending on the resource, stripping them out is a real chunk of work.

And it gets noisier. Azure also backfills defaults on writable properties, null here, an empty array there, a disabled flag for a feature you never touched. A VNet you never peered still shows up with "virtualNetworkPeerings": []. The AzureRM Terraform exporter is the worst offender; empty values are everywhere.

2. "Masked sensitive attribute" makes the noise actively misleading

Here's where it stops being annoying and starts being a problem. When a property is marked secure (the kind you'd wrap with @secure() in Bicep) and happens to be empty, the AzureRM exporter writes Masked sensitive attribute into the file. So now you're staring at a value you can't see, and you have no way to tell whether it was actually configured in the original deployment or whether it was never set at all.

The same masking shows up on read-only fields too, a storage account's primary_access_key, for instance. That should never be in deployment code in the first place. At this point you're not translating a template, you're investigating one.

3. Implicit child resources inflate the template

Create a resource in Azure and you usually create a few others alongside it. The SQL Server case is the one that always gets me: deploy a single server with one database, run the export, and you'll see more than 30 resources for that one server. Auto-provisioned children with disabled configurations, plus the master database's entire subtree.

That last part is actually a bug on Azure side (not export feature). The master database itself is managed by Azure and gets correctly filtered out of the resource group export list. But its child resources don't inherit the managedBy property in their ARM templates, so they slip through and end up in your export anyway. You're cleaning up resources that Azure itself considers Azure-managed.

Terraform AzureRM and Bicep pile on with their own duplication: child resources show up twice, once inlined inside the parent, once again as standalone resources. Subnets under a VNet are the textbook example. AKS node pools do the same thing. So before the template even validates, you're pruning auto-generated noise and deduplicating.

4. Parametrization is barely there

Only Bicep makes any attempt at parametrization, and even then it's just resource names. Resource references, the things that should be using the resourceId() family of functions, come out as hardcoded strings inside the parameter file. Anything that actually makes IaC reusable across environments, SKUs, regions, or accounts? You're writing that yourself.

5. Secrets are your problem to solve

Sensitive values are stripped from the export, which is the right call. The catch is that you have to know which properties are sensitive in the first place. A VM admin password is obvious. The commandToExecute property on a VM extension isn't, it's just a string field, but it's quite possible highly sensitive information is passed in. Nothing in the export flags it as something that needs to go through Key Vault. You're expected to already know.

6. Permission gaps in aztfexport

Some resources tend to disappear from the export. Key Vault secrets, Storage Account file shares, Storage Account queue services, among others. The issue is a downstream consequence of how aztfexport works.

Because the Terraform path runs through terraform import and the AzureRM provider, the export inherits the provider's authentication context. Whatever permissions you've granted the provider's identity are the only permissions the export can use. And the data-plane permissions needed to read these specific resources are typically not part of a provider's role assignment, for good reason. Following least-privilege RBAC practice, you wouldn't grant your Terraform identity Key Vault Secrets User on every vault, or Storage Account Key Operator across every storage account. Most teams don't, and shouldn't.

When the export hits one of these resources without the right permission, it doesn't fail silently. It throws an import error, but the error itself is layered and doesn't always point at the actual cause. Even when you do grant what looks like the correct permission, you can still hit problems. In one of my own runs, after granting the provider Key Vault Secrets User on the target vault, the export still failed on some secrets with a 401 Unauthorized coming from the vault listing call at the subscription level, not from the secret read itself. The provider needs to resolve the vault before it can read the secret, and that resolution step requires its own management-plane permission. The error message names the secret being imported, but the root cause is two layers up.

The information is in the raw error if you know how to read it. Most people don't, the first time they hit it. And in a resource group with dozens of vaults, storage accounts, and other permission-sensitive resources, you're doing this diagnosis once per resource type, sometimes once per resource.

7. Everything the export doesn't even try to do

And this is just the cleanup work. I haven't even touched what the export makes no attempt at in the first place.

Every reference is a hardcoded string. Outputs and cross-deployment wiring don't exist either. Implicit dependencies are flattened too, if one resource references another's output, that reference is also a dependency, but the export won't reconstruct any of that intent.

Modularization doesn't happen, no Bicep or Terraform modules, just one flat file.

None of this means the Export template feature has no place. For a small, mostly-greenfield resource group, something you're spinning up to learn from, prototype with, or throw away. It'll get you a working starting point faster than writing from scratch. But once you're talking about IaC you actually intend to deploy, version, and maintain, the distance between "exported template" and "code you'd want to own" is bigger than it looks. And most of the work that closes the gap is exactly the kind of work that isn't documented anywhere.

Enterprise Grade Azure Management

@2026 Clophi all right reserved.

Information

Company

Features

Enterprise Grade Azure Management

Information

Pricing

Docs

Privacy Statement

Terms Of Service

Company

About Us

Contact our team for your need

Request a demo

Professional Services

Features

Drift Detection

Enterprise Policy

Infrastructure Repository Generator

Policy Repository Generator

Infrastructure As Code

Server Configuration

Devops Tooling

Azure Integration

Built-In Solutions & Training

@2026 Clophi all right reserved.