terraformazureiaccafnaming conventions

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.

AzureNamer Team ·

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 →

Try AzureNamer

Generate CAF-compliant names for all 203 Azure resource types — free, no login required.

Open the Generator →