ARM Resource Aliases with Terraform
Small correction to the usual confusion around this topic: ARM aliases are not friendly names for Azure resources. They are paths that Azure Policy uses to target resource properties. That makes them useful with Terraform, but not in the way many people first expect.
When people hear "resource alias" in Azure, it is easy to think of a pointer to a resource. Something like alias-storage-main that redirects to a real storage account ID.
That is not what ARM aliases are.
In Azure Resource Manager, aliases are mostly used by Azure Policy. They map a policy-friendly name to a property path inside a resource type. For example, a policy can use an alias to check whether a storage account allows public access, whether a tag exists, or whether a specific property has the expected value.
So the practical Terraform use case is not "create an alias and point it to a resource".
The practical use case is:
use ARM aliases inside Azure Policy definitions that you manage with Terraform.
That is less flashy, but much more useful.
What ARM aliases actually are
An ARM alias is a reference to a property on an Azure resource type.
You can think of it as a stable-ish policy path that Azure Policy understands. Instead of writing custom logic for every resource shape, Azure Policy can evaluate aliases exposed by the resource provider.
Examples of things aliases are used for:
- checking tags
- enforcing locations
- auditing storage account settings
- requiring secure transfer
- blocking public network access
- validating diagnostic settings or SKU choices where supported
They are not DNS aliases. They are not endpoints. They are not a shortcut resource ID. They do not let you move a storage account and magically keep all dependent resources working.
That distinction matters, because otherwise the Terraform design goes in the wrong direction.
Why they matter with Terraform
Terraform is often used to manage Azure Policy definitions, policy initiatives and assignments.
That is where ARM aliases become useful.
You can write a custom policy definition in Terraform and use aliases in the policy rule. Then you assign the policy to a management group, subscription or resource group.
This helps when you want to enforce rules like:
- storage accounts must not allow public blob access
- resources must have required tags
- only approved regions are allowed
- insecure configurations should be denied or audited
- specific resource properties must match your platform baseline
In other words: aliases are not an IaC refactoring tool. They are a governance tool.
Finding aliases
Before writing a policy, check which aliases Azure exposes for the resource type.
With Azure CLI, you can inspect aliases like this:
az provider show \
--namespace Microsoft.Storage \
--expand "resourceTypes/aliases" \
--query "resourceTypes[?resourceType=='storageAccounts'].aliases[].name" \
--output table
For a broader look:
az provider show \
--namespace Microsoft.Storage \
--expand "resourceTypes/aliases" \
--output json
The exact aliases depend on the provider and API surface. Do not guess them. Check what Azure exposes, then use that in the policy.
Terraform example: deny public blob access
Here is a small example that manages a custom Azure Policy definition with Terraform.
The policy denies storage accounts where public blob access is enabled.
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = "rg-policy-demo"
location = "westeurope"
}
resource "azurerm_policy_definition" "deny_public_blob_access" {
name = "deny-public-blob-access"
policy_type = "Custom"
mode = "Indexed"
display_name = "Deny public blob access on storage accounts"
description = "Denies storage accounts that allow public blob access."
policy_rule = jsonencode({
if = {
allOf = [
{
field = "type"
equals = "Microsoft.Storage/storageAccounts"
},
{
field = "Microsoft.Storage/storageAccounts/allowBlobPublicAccess"
equals = true
}
]
}
then = {
effect = "deny"
}
})
}
resource "azurerm_resource_group_policy_assignment" "deny_public_blob_access" {
name = "deny-public-blob-access"
resource_group_id = azurerm_resource_group.rg.id
policy_definition_id = azurerm_policy_definition.deny_public_blob_access.id
}
The important line is this one:
field = "Microsoft.Storage/storageAccounts/allowBlobPublicAccess"
That field value is the alias Azure Policy evaluates.
Example: require a tag
For tag rules, the alias pattern is simpler.
This policy denies resources that do not have an owner tag:
resource "azurerm_policy_definition" "require_owner_tag" {
name = "require-owner-tag"
policy_type = "Custom"
mode = "Indexed"
display_name = "Require owner tag"
policy_rule = jsonencode({
if = {
field = "tags['owner']"
exists = false
}
then = {
effect = "deny"
}
})
}
This is the kind of policy I actually like to manage through Terraform, because the definition, assignment and scope can be reviewed like normal infrastructure code.
Common mistake: treating aliases like resource pointers
This is the part I would be careful with.
There is no normal Terraform pattern where you create something like this and use it as a movable pointer to a storage account:
resource "azurerm_resource_alias" "example" {
name = "alias-storage-main"
target_resource_id = azurerm_storage_account.sa.id
}
That is not how ARM aliases work.
If you need stable references between Terraform modules, use normal Terraform outputs, remote state, data sources, naming conventions, or a platform registry pattern. If you need traffic redirection, use DNS, Private DNS, Front Door, Application Gateway, Traffic Manager or service-specific endpoints.
ARM aliases solve a different problem: policy evaluation.
Practical workflow
My workflow for this is usually:
- Decide which Azure property you want to govern.
- Check whether the resource provider exposes an alias for it.
- Write a small custom policy definition.
- Manage the policy definition and assignment in Terraform.
- Test the policy in
auditmode first. - Switch to
denyonly when the false positives are understood.
That last step is important. A broken deny policy can block deployments in annoying ways. Start with audit unless you are completely sure.
Pitfalls
A few things to watch:
- Alias support is not identical for every resource type.
- Some properties are only available in certain API versions.
Indexedmode andAllmode behave differently.- Deny policies can break pipelines if tested poorly.
- Tags are easy to enforce badly, especially on inherited or generated resources.
- Policy evaluation and Terraform planning are separate worlds. Terraform may plan successfully and Azure Policy may still deny the deployment.
That last one catches teams often. Terraform tells you what it wants to create. Azure Policy decides whether Azure will allow it.
My take
ARM aliases are useful, but the name is misleading if you come from an infrastructure refactoring mindset.
They are not friendly names for resources. They are not a way to swap out a storage account behind a stable alias. They are Azure Policy property paths.
Used correctly, they are valuable. You can manage custom policies in Terraform, use aliases to target specific Azure properties, and keep governance rules versioned with the rest of your platform code.
Used incorrectly, they lead to fake abstractions and Terraform examples that look nice but do not map to how Azure actually works.
So my rule is simple: use ARM aliases for policy. Use Terraform outputs, data sources, DNS or service-specific endpoints for resource references.
References
- Azure Policy definition structure: https://learn.microsoft.com/en-us/azure/governance/policy/concepts/definition-structure-policy-rule
- Azure Policy alias basics: https://learn.microsoft.com/en-us/azure/governance/policy/concepts/definition-structure-alias
- Terraform
azurerm_policy_definition: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/policy_definition - Terraform
azurerm_resource_group_policy_assignment: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group_policy_assignment