Microsoft Azure resource naming conventions are one of those “small” DevOps and cloud infrastructure management disciplines that quietly determine whether your cloud estate stays governable at 50 resources—or collapses into entropy at 5,000 resources. In this article, you’ll build a repeatable, region-aware naming strategy using HashiCorp Terraform and the Build5Nines/naming/azure module (which wraps Microsoft’s Azure/naming/azurerm module) to generate compliant names consistently across Azure resource types.
The goal isn’t to debate what the perfect naming convention is. The goal is to implement your convention in a way that scales across teams, subscriptions, regions, and multi-region DR patterns—without relying on tribal knowledge or hand-crafted strings.
Two trends make this more relevant than ever:
- Policy-driven governance and FinOps: naming is still one of the fastest ways to segment cost, ownership, environment, and blast radius—especially when tags are missing or inconsistent.
- Multi-region architectures: even when a workload is “single-region,” the platform usually isn’t. Azure’s region pairing guidance influences replication and DR strategies, and region-aware naming is a practical way to reduce confusion during incidents.
Understanding the Core Technology
At scale, naming fails for predictable reasons: Azure resources have different rules (allowed characters, maximum lengths, global uniqueness, dash support, etc.), and many resource types can’t be renamed without replacement. Azure documents naming rules and restrictions per resource type, and those constraints are the reason “just concatenate a few strings” eventually breaks down.
Terraform modules help by moving naming from “convention” into code. The Build5Nines module builds on Microsoft’s official naming module and adds opinionated, organization-friendly defaults—especially around region abbreviations and region pairing.
Here’s the conceptual shape:
- Microsoft’s module (
Azure/naming/azurerm) provides resource-type-aware naming logic and constraints. - Build5Nines module (
Build5Nines/naming/azure) wraps it, and extends it, adding:- standardized region abbreviation resolution
- tokenized suffix and prefix patterns (org/loc/env) for defining custom naming conventions
- Azure Region pair awareness for DR and multi-region management
A key design detail: the module returns a module object containing naming info for many resource types (not just a single string). That means you can access names (and constraints) per resource type.
# Example access pattern
module.azure_primary.resources.resource_group.name
module.azure_primary.resources.storage_account.name
module.azure_primary.resources.key_vault.name_unique
- resources.<type>.name gives a convention-based name for that resource type.
- resources.<type>.name_unique provides a unique variant where appropriate (helpful for globally-unique resources).
- You can also inspect constraints (like max_length, dashes, and regex) to prevent surprises.
In the next section, you’ll set up a baseline Terraform environment and wire in naming as a first-class dependency—so every workload module consumes the same standard.
Setting Up Your Environment
This section walks through a practical baseline: Terraform + Azure provider authentication + a project layout where naming is defined once and reused everywhere. The objective is to reduce “snowflake configuration” and make naming a shared platform concern.
Tools and dependencies
You’ll need:
- HashiCorp Terraform
- Azure CLI (for authentication and subscription context)
- Terraform provider:
hashicorp/azurerm
- Optional but recommended:
- a CI runner with OIDC (GitHub Actions / Azure DevOps) to avoid long-lived secrets
Authenticate to Azure (developer workstation)
az login
az account set --subscription "<YOUR_SUBSCRIPTION_ID>"
az account show --output table
- Signs you into Azure using the browser flow.
- Sets your active subscription (important when you have multiple).
- Verifies the current tenant/subscription context.
Terraform provider baseline
Create providers.tf:
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 4.57.0"
}
}
}
provider "azurerm" {
features {}
}
- Pins Terraform and provider versions to avoid “it worked yesterday” drift.
- Configures the AzureRM provider with the standard features
{}block.
At this point, you have a working Terraform/Azure baseline. Next, you’ll implement naming as a reusable foundation and then extend it to workloads and paired-region deployments.
Building the Solution
In this section you’ll implement a naming standard that:
- is consistent across resources and teams,
- can be customized to match your organization’s convention, and
- supports paired regions for DR and multi-region builds.
Step 1: Define a naming convention for your Azure resources
Start by deciding what must be encoded in names versus what belongs in tags. Microsoft’s Cloud Adoption Framework (CAF) recommends creating a standard naming convention (and provides resource abbreviations you can align with).
A good “contract” for SRE/DevOps teams is:
- Names encode: org, environment, region (abbreviated), optional workload identifier
- Tags encode: cost center, owner team, service tier, data classification, etc.
Create variables.tf:
variable "organization" {
description = "Short org identifier (e.g., b59)"
type = string
}
variable "environment" {
description = "Environment name (e.g., dev, test, prod)"
type = string
}
variable "location" {
description = "Azure region display name or short name (e.g., 'East US' or 'eastus')"
type = string
}
variable "workload" {
description = "Optional workload/app identifier (e.g., api, payments, data)"
type = string
default = null
}
- Establishes the minimum inputs needed for consistent naming.
- Adds an optional workload dimension you can incorporate into naming patterns.
Step 2: Instantiate the Build5Nines naming module (primary region)
Create naming.tf:
module "azure_primary" {
source = "Build5Nines/naming/azure"
organization = var.organization
environment = var.environment
location = var.location
}
- Defines your naming engine once for the entire deployment.
- Produces standardized names for many Azure resource types via module.azure_primary.resources.
- Resolves region abbreviations and exposes them as outputs you can reuse.
Now wire it into a resource group:
resource "azurerm_resource_group" "main" {
name = module.azure_primary.resources.resource_group.name
location = module.azure_primary.location
}
- Ensures the RG name follows the convention automatically.
- Uses the module’s canonicalized location output as the authoritative region value.
Step 3: Customize your naming convention with tokenized suffix patterns
Out of the box, the module composes an opinionated suffix pattern using tokens like {org}, {loc}, and {env}. You can customize, reorder these and add literals to match your standard if you want to override the default:
module "azure_primary" {
source = "Build5Nines/naming/azure"
organization = var.organization
environment = var.environment
location = var.location
# Optionally, set name_suffix to customize naming convention
# Example: add workload when provided; otherwise omit at call sites.
name_suffix = var.workload == null
? ["{org}", "{loc}", "{env}"]
: ["{org}", "{loc}", "{env}", var.workload]
}
- Keeps naming consistent while still supporting per-workload differentiation.
- Uses token expansion and a list-based pattern (which also aligns with the upstream module interface).
Suffix-based conventions are common because they read naturally (rg-org-loc-env), but at scale you may prefer a prefix-first convention where the most important grouping elements (like workload/application) lead the name. This can make Azure Portal lists, logs, and dashboards easier to scan because related resources cluster together alphabetically.
The Build5Nines naming module supports this by letting you set a name_prefix (and, if desired, reduce or remove name_suffix):
module "azure_primary" {
source = "Build5Nines/naming/azure"
organization = var.organization
environment = var.environment
location = var.location
# Optionally, set name_prefix to customize naming convention
# Example: add workload when provided; otherwise omit at call sites.
name_prefix = var.workload == null
? ["{org}", "{loc}", "{env}"]
: ["{org}", "{loc}", "{env}", var.workload]
# Override and clear name_suffix default, since we're using name_prefix.
name_suffix = []
}
- Sets a prefix list that the module prepends to resource names.
- Remove the default suffix pattern.
- Uses an empty list when workload is not provided, avoiding awkward leading separators.
Step 4: Build region-pair-aware naming for DR and multi-region patterns
Azure defines “region pairs” for some resiliency and replication behaviors. You don’t choose the pairs—Microsoft defines them—and they matter when you design DR (Disaster Recovery) strategies or geo-redundant services.
The Build5Nines module exposes location_secondary, derived from the Azure Region Pair data, which you can use to instantiate a second naming module for the paired region.
module "azure_secondary" {
source = "Build5Nines/naming/azure"
organization = module.azure_primary.organization
environment = module.azure_primary.environment
# Paired/canonical secondary region
location = module.azure_primary.location_secondary
}
- Creates a consistent naming “mirror” for the paired region.
- Ensures primary and secondary deployments use the same org/env rules, but correct region context.
- Enables you to produce DR resources with names that clearly indicate regional placement.
Step 5: Name real resources (and handle Azure’s “no dashes” constraints)
Different resource types have different naming restrictions—storage accounts are the classic example (lowercase alphanumeric, no dashes, strict length limits). Azure documents these rules centrally, and the upstream naming module encodes many of them.
Here’s an example pattern from the module README: append a workload-specific literal while still basing the name on the resource type’s naming rules.
resource "azurerm_storage_account" "files" {
name = "${module.azure_primary.resources.storage_account.name}files"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "GRS"
}
- Uses the module-generated storage account base name (constraint-aware).
- Appends a short suffix (files) to differentiate purpose without inventing a new convention.
- Keeps the “naming brain” centralized in one place.
And a resource type where dashes are allowed:
resource "azurerm_mssql_server" "data" {
name = "${module.azure_primary.resources.sql_server.name}-data"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
version = "12.0"
administrator_login = "dataadministrator"
administrator_login_password = "REPLACE_ME"
}
- Uses the correct resource-type abbreviation and pattern via the module.
- Appends a dash-separated qualifier (-data) for clarity.
Step 6: Use unique names where global uniqueness matters
Some Azure resource types require globally unique names (or you simply want to avoid collisions across environments/subscriptions). The naming module exposes name_unique for that purpose.
resource "azurerm_key_vault" "main" {
name = module.azure_primary.resources.key_vault.name_unique
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
}
- Generates a unique Key Vault name variant while preserving the convention.
- Reduces the risk of “name already taken” failures in CI/CD.
Step 7: Add guardrails by inspecting constraints in Terraform
One of the most practical “SRE-grade” moves is to turn naming constraints into feedback early—before Microsoft Azure rejects a plan at apply time. The module exposes per-resource metadata such as max_length.
locals {
sa_max = module.azure_primary.resources.storage_account.max_length
sa_len = length(module.azure_primary.resources.storage_account.name)
}
check "storage_account_name_length" {
assert {
condition = local.sa_len <= local.sa_max
error_message = "Storage account base name is too long: ${local.sa_len} > ${local.sa_max}"
}
}
- Reads the storage account naming limit from the module (not from memory).
- Uses Terraform’s check block to fail fast with a clear message.
- Helps teams safely evolve suffix patterns without accidental breakage.
At this point you’ve implemented a naming system that is deterministic, reusable, and region-pair-aware. You can drop this into a shared “platform” layer and have every workload module consume it through inputs/outputs instead of re-implementing it.
Debugging and Optimization
Troubleshooting naming issues is usually less about Terraform and more about subtle Azure constraints. This section focuses on practical failure modes you’ll see in real pipelines and how to harden your implementation.
Common issues and how to diagnose them
- Azure rejects the name at apply time: Cross-check the resource type’s rules (allowed characters, length, uniqueness scope). Azure’s naming rules documentation is a solid baseline reference.
- Storage account / DNS-style name failures: If your convention uses dashes heavily, ensure the pattern for dash-restricted resources stays compatible. Prefer short workload literals and use constraint checks (as shown earlier).
- Unexpected region abbreviations: Standardize the location input at your platform boundary (for example, always pass display names like
East USor always pass canonical names likeeastus) and only override abbreviations when you truly need to. The module includes a region abbreviation mapping and supports overrides. - Secondary region doesn’t match your DR design: Remember, Microsoft Azure region pairs are Microsoft-defined and not always what teams expect. Validate the pairing assumptions against Microsoft’s region pairing guidance.
Optimization tip: treat naming as a platform module
If you’re operating at scale:
- Put the naming module behind a thin internal wrapper module (for your org defaults).
- Expose only the inputs you allow (org/env/region/workload) and export
resources,base_suffix, andlocation_secondary. - Version it like any other platform dependency.
With these guardrails, naming becomes a solved problem—repeatable in local dev, consistent in CI, and predictable during incidents.
Conclusion
You implemented a scalable Azure naming strategy using Terraform and the Build5Nines/naming/azure module, leveraging resource-type-aware naming rules, standardized region abbreviations, and region pair awareness for DR and multi-region deployments.
The big takeaway for DevOps and SRE teams is that naming conventions only work when they’re enforced by automation—especially given Azure’s per-resource naming constraints and the operational cost of renaming.
From here, useful enhancements include:
- Add organization-wide policy checks (naming + tags) in CI before apply.
- Centralize convention changes behind a versioned internal module and roll them out like any other platform update.
- Extend the pattern to produce consistent names for cross-subscription and multi-tenant deployments by standardizing org/env/workload inputs at the platform boundary.
If you’d like, I can also provide a “platform wrapper” module pattern (with a minimal interface) that teams can consume without ever touching name_suffix directly—ideal for large organizations with multiple delivery teams.
Original Article Source: Implementing Azure Naming Conventions at Scale with Terraform and Build5Nines/naming/azure (AzureRM + Region Pairs) written by Chris Pietschmann (If you're reading this somewhere other than Build5Nines.com, it was republished without permission.)

Microsoft Azure Regions: Interactive Map of Global Datacenters
Create Azure Architecture Diagrams with Microsoft Visio
Unlock GitHub Copilot’s Full Potential: Why Every Repo Needs an AGENTS.md File
Configuring GitHub Actions to Run Jobs on Specific Branches
IPv4 Address CIDR Range Reference and Calculator




