Naming Azure Resources in Terraform: A CAF-Compliant Approach
How to implement Microsoft CAF naming conventions in Terraform using locals — including handling Storage Accounts, Key Vaults, and other resources with tight character limits.
Infrastructure-as-code makes Azure resource names more consequential, not less. When you deploy with Terraform, the name in your .tf file is the name in Azure — and unlike manually-created resources, IaC deployments are expected to be consistent, repeatable, and reviewable in pull requests. A naming convention isn’t optional here; it’s the foundation of a maintainable Terraform codebase.
This guide shows how to implement CAF-compliant Azure naming in Terraform using locals, handle the awkward edge cases (Storage Accounts, Key Vaults, VMs), and avoid the pitfalls that trip up most teams.
Why names matter more in Terraform
When you create a resource manually in the Azure portal, you can see the name field and second-guess yourself before clicking Create. In Terraform, names are generated by string expressions buried in .tf files — by the time you catch an error, terraform apply has already run.
More critically: most Azure resources cannot be renamed after creation. If your naming pattern is wrong, fixing it means terraform destroy followed by terraform apply — with all the state management, dependency re-creation, and potential downtime that implies.
Define your naming convention in locals before writing a single resource block.
The locals naming pattern
The CAF naming pattern follows this component order:
{type}-{company}-{department}-{workload}-{environment}-{region}-{instance}
In Terraform, centralise this in a locals block:
locals {
company = "contoso"
department = "finance"
workload = "payments"
env = "prod"
region = "eus"
instance = "001"
# Standard name: type + all components
name_base = "${local.company}-${local.department}-${local.workload}-${local.env}-${local.region}-${local.instance}"
# Names per resource type
resource_group_name = "rg-${local.name_base}"
vnet_name = "vnet-${local.name_base}"
aks_name = "aks-${local.name_base}"
acr_name = "cr-${local.name_base}"
app_service_name = "app-${local.name_base}"
sql_server_name = "sql-${local.name_base}"
}
Then in your resource blocks:
resource "azurerm_resource_group" "main" {
name = local.resource_group_name
location = "eastus"
}
resource "azurerm_kubernetes_cluster" "main" {
name = local.aks_name
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
# ...
}
Handling tight length limits
Some resource types have character limits that are easy to exceed. These need their own truncated locals.
Storage Accounts — 24 chars, lowercase, no hyphens
Storage Accounts are the most constrained: 24 characters maximum, lowercase letters and numbers only, no hyphens. The standard name_base approach won’t work.
locals {
# Storage Account: strip hyphens, lowercase, max 24 chars
# Drop department to fit, or truncate workload
storage_name = lower(substr(
"st${local.company}${local.workload}${local.env}${local.instance}",
0, 24
))
}
For contoso + payments + prod + 001 this gives: stcontosopaymentsprod0 (22 chars — fits).
If your company or workload names are longer, trim one first:
locals {
# Explicit shorter variant for storage
storage_name = "st${substr(local.company, 0, 6)}${substr(local.workload, 0, 8)}${local.env}${local.instance}"
}
Key Vault — 24 chars, alphanumerics + hyphens
Key Vault allows hyphens but still has a 24-character limit. Drop lower-priority components:
locals {
# Key Vault: drop department and region, keep type+company+workload+env+instance
key_vault_name = "kv-${local.company}-${local.workload}-${local.env}-${local.instance}"
}
For contoso + payments + prod + 001: kv-contoso-payments-prod-001 = 28 chars — still over. Drop company:
key_vault_name = "kv-${local.workload}-${local.env}-${local.instance}"
# kv-payments-prod-001 = 20 chars ✓
Windows Virtual Machines — 15 chars
Windows VM names are restricted to 15 characters (NetBIOS limit). Use only abbreviation + minimal identifiers:
locals {
# VM: type (2) + workload short (4) + env (4) + instance (3) = 13 chars
vm_name = "vm${substr(local.workload, 0, 4)}${local.env}${local.instance}"
# vmpaypprod001 = 13 chars ✓
}
Validating names with precondition
Terraform 1.2+ supports precondition blocks in resource lifecycle. Use them to catch naming violations at plan time:
resource "azurerm_storage_account" "main" {
name = local.storage_name
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "LRS"
lifecycle {
precondition {
condition = length(local.storage_name) <= 24
error_message = "Storage account name '${local.storage_name}' exceeds 24 characters."
}
precondition {
condition = can(regex("^[a-z0-9]+$", local.storage_name))
error_message = "Storage account name must be lowercase alphanumerics only."
}
}
}
This fails loudly at terraform plan instead of at terraform apply, saving you from a partial deployment.
Reusable naming module
For multi-workload environments, extract naming into a Terraform module:
# modules/naming/variables.tf
variable "company" { type = string }
variable "workload" { type = string }
variable "env" { type = string }
variable "region" { type = string }
variable "instance" { type = string, default = "001" }
# modules/naming/outputs.tf
output "resource_group" { value = "rg-${var.company}-${var.workload}-${var.env}-${var.region}-${var.instance}" }
output "key_vault" { value = "kv-${var.workload}-${var.env}-${var.instance}" }
output "storage_account" { value = lower(substr("st${var.company}${var.workload}${var.env}${var.instance}", 0, 24)) }
output "aks" { value = "aks-${var.company}-${var.workload}-${var.env}-${var.region}-${var.instance}" }
# Usage
module "names" {
source = "./modules/naming"
company = "contoso"
workload = "payments"
env = "prod"
region = "eus"
}
resource "azurerm_resource_group" "main" {
name = module.names.resource_group
location = "eastus"
}
Generating the names
Before writing your locals, use AzureNamer to generate CAF-compliant names for every resource type in your deployment. Enter your company, workload, environment, and region once — then copy or export the full table as CSV to paste directly into your Terraform locals.
Try AzureNamer
Generate CAF-compliant names for all 203 Azure resource types — free, no login required.
Open the Generator →