Azure Terraform Conventions: How to Design & Enforce a Real-World Naming Strategy

Repository: https://github.com/SalehElnagar/azure-terraform-conventions

This article walks through how to think about Azure naming conventions and how to turn those decisions into code using the azure-terraform-conventions GitHub repository. That repo contains:

  • A small, focused conventions library under conventions/modules/ that standardizes naming and tagging for Azure resources.
  • A set of Azure resource modules (VNets, VMs, AKS, Azure Firewall, etc.) that are built to plug into those conventions and expose normalized names and tags.

The goal is not “just use whatever the repo does”. The goal is: capture your organization’s naming decisions once, codify them with this library, and then let every Terraform module inherit them automatically.


Who this article is for

This guide is written for:

  • DevOps and platform engineers responsible for Azure landing zones
  • Cloud / solution architects defining standards across teams
  • DevSecOps and security teams enforcing policy and compliance
  • CTOs and engineering leaders who care about scale, governance, and cost visibility

We’ll stay practical: you’ll see how to think about naming, how that maps to the azure-terraform-conventions repo, and how to use the modules in your own code.


Why Azure naming conventions actually matter

On Azure, naming is not just aesthetics. It affects:

  • Governance: Azure Policy is dramatically easier to write if environments, regions, and workloads are obvious from the name.
  • Security: Access reviews, incident response, and threat hunting all depend on being able to quickly answer “what is this resource and which environment does it belong to?”
  • Cost management: Names and tags feed cost reports, chargeback/showback, and budgeting.
  • Automation: Scripts and pipelines often rely on patterns in names and tags to scope their actions.
  • Human sanity: When you open the Azure Portal and see rg-weu-payments-prod, you know exactly what it is. rg-12345… not so much.

Microsoft’s own Cloud Adoption Framework recommends designing a consistent naming convention that captures key dimensions like resource type, workload, environment, and instance number, in a fixed order with clear abbreviations.

The azure-terraform-conventions repo takes that idea and turns it into reusable Terraform modules so you can enforce your naming strategy as code instead of as a wiki page everyone forgets about.


How to think about your Azure naming convention (before touching the repo)

Before you plug in any module, you should be clear on what you want your names to express and for whom. A good naming convention usually answers three questions:

  1. What context does every engineer need to see at a glance?
  2. What do tools and policies need to match on reliably?
  3. What are Azure’s hard technical limits (length, allowed characters, uniqueness)?

1. Decide the information that must be encoded

Most teams end up with some version of the following building blocks:

  • Environmentdev, test, stage, prod, etc.
  • Regionwesteurope, switzerlandnorth, etc., often shortened to weu, chn, and so on.
  • Workload / namespace – a business or platform domain (e.g. payments, platform, shared).
  • Resource purpose – what it actually does (e.g. vnet, aks, appgw, kv, sql).
  • Instance / sequence01, 02 (useful if you run multiple instances of the same pattern).

A typical “mental model” for a resource name might look like:

<resource-prefix>-<region-code>-<namespace>-<environment>[-<purpose>][-<instance>]

For example:

  • rg-weu-payments-prod – production resource group for the Payments workload in West Europe.
  • vnet-weu-payments-prod-shared – shared VNet for Payments in prod, West Europe.
  • aks-weu-payments-prod-01 – first AKS cluster for Payments in prod, West Europe.

The exact pattern is your decision. The azure-terraform-conventions repo gives you a way to implement and enforce that pattern consistently.

2. Choose what goes in the name vs in tags

Names are short and constrained. Tags are flexible key/value metadata. Use them together:

  • Names should carry the minimum context needed for humans and policies: region, environment, workload, and role.
  • Tags should carry richer metadata: Environment, Application, Owner, CostCenter, DataClassification, etc.

A nice rule of thumb:

  • If something is frequently used for scoping (RBAC, policies, search), it likely belongs in the name and tags.
  • If it’s mostly for reporting, it can live only in tags.

3. Standardize abbreviations and codes

To keep names short and consistent:

  • Define a canonical list of region codes (e.g. westeurope -> weu, switzerlandnorth -> chn).
  • Define environment codes (dev, tst, prd or similar) and stick to them.
  • Agree on resource prefixes (rg, vnet, aks, fw, kv, etc.).

The conventions/modules/common module in the repo is designed exactly for this: it centralizes canonical maps for Azure regions and environments so you’re not inventing codes ad‑hoc in each module.

4. Fix the label order and delimiter

Once you know your components and abbreviations, you need to standardize:

  • Order – e.g. prefix-region-namespace-env-purpose-instance
  • Delimiter – usually a hyphen (-) for readability, except for resource types that forbid it (storage accounts, etc.).

This is where using a shared naming module is so powerful: you define the order in one place, and all consumers get the same pattern.

5. Validate against Azure rules

Azure applies length and character restrictions that differ by resource type. Your convention should:

  • Keep names short enough to work for “strict” resources (storage accounts, SQL, etc.).
  • Avoid unsupported characters (some resources don’t allow -, others require lowercase, etc.).

In practice, this means regular expressions and length checks somewhere in your tooling. In this repo, those checks live inside the conventions modules so Terraform fails early instead of Azure failing late.


What lives inside azure-terraform-conventions?

At a high level, the repo gives you two things:

1. The conventions library

Under conventions/modules/ you’ll find small, focused modules:

  • common – canonical maps and shared data for Azure regions and environments.
  • resource – a deterministic name builder for Azure resources.
  • resource_group – opinionated naming for Azure Resource Groups (typically using an rg-style prefix).
  • tags – consistent tag generation from a minimal “metadata” object.

The idea is simple: call these modules once per stack, get back normalized strings and tag maps, and then feed those outputs into all your actual resources and modules.

2. Azure resource modules

The root of the repo contains ready-to-use modules such as:

  • Networking: virtual_network, vpn_gateway, application_gateway, nat_gateway, load_balancer, azure_firewall, virtual_network_peering, etc.
  • Compute: linux_vm, windows_vm, linux_vmss.
  • Containers: full_aks_cluster and a container_registry helper module.
  • Database: mssql_server.
  • Utilities: azure_cloud, azure_location, remote_state, and resource_id_parser.

These resource modules are designed to work with the conventions library so that resource names and tags are generated in a predictable way and exposed as outputs you can reuse in other parts of your stack.


Designing your own naming convention with this repo (step by step)

Let’s walk through how you might adopt the repo in a real platform, while keeping control over your own naming rules. We’ll use a fictional “payments” workload in westeurope as an example.

Step 1 – Add the repo to your stack

You can vendor the repo into your own Git organization, add it as a Git submodule, or reference it directly as a Terraform module source. For example:

module "resource_naming" {
  # You can also point to a local path if you prefer
  source = "github.com/SalehElnagar/azure-terraform-conventions//conventions/modules/resource?ref=main"

  # <-- we'll talk about the inputs in the next step
}

The exact source value is up to your workflow (internal mirror, tag, branch, etc.), but the structure //conventions/modules/resource matches the repo layout.

Step 2 – Capture your naming decisions in one place

Start by encoding your decisions about environment names, regions, and workload namespaces in a small set of locals. Then pass those into the conventions modules.

locals {
  # Org-wide naming decisions
  environment   = "prod"
  environment_code = "prd"

  region        = "westeurope"
  region_code   = "weu"

  namespace     = "payments"       # business or platform domain
  workload_name = "payments-api"   # more specific application name
}

# Example: generate a base name for resources in this stack
module "resource_naming" {
  source = "github.com/SalehElnagar/azure-terraform-conventions//conventions/modules/resource?ref=main"

  # Check variables.tf in the module for the exact object shape.
  # Conceptually, you're passing a structured "naming" object like:
  # naming = {
  #   region_code   = local.region_code
  #   environment   = local.environment
  #   namespace     = local.namespace
  #   workload_name = local.workload_name
  #   prefix        = "aks"
  #   instance      = "01"
  # }
}

Important: The snippet above is illustrative. Check the variables.tf file in conventions/modules/resource inside the repo to match the exact input names and types. The pattern, though, is always the same: you pass in structured naming metadata; the module emits a normalized resource name.

Do the same for tags using the conventions/modules/tags module:

locals {
  tags_metadata = {
    environment   = local.environment
    application   = local.workload_name
    owner         = "team-payments"
    cost_center   = "CC-1234"
    # anything else your org mandates
  }
}

module "base_tags" {
  source = "github.com/SalehElnagar/azure-terraform-conventions//conventions/modules/tags?ref=main"

  # Again, check the module's variables; typically you'll pass a single
  # metadata object and optionally some "extra" tags.
}

The key idea: you express your naming strategy once as data. The modules handle composing strings, applying abbreviations, normalizing case, and merging organization-wide tags with workload-specific tags.

Step 3 – Generate resource group names

Resource Groups usually follow a slightly different pattern (but they should still be consistent). The conventions/modules/resource_group module is there for that.

module "rg_naming" {
  source = "github.com/SalehElnagar/azure-terraform-conventions//conventions/modules/resource_group?ref=main"

  # Conceptually something like:
  # naming = {
  #   prefix        = "rg"
  #   region_code   = local.region_code
  #   environment   = local.environment
  #   namespace     = local.namespace
  # }
}

You’d then use the output of this module when creating your actual RG in Terraform. The exact output names are defined in outputs.tf of that module, but they will typically include a normalized RG name.

Step 4 – Wire conventions into your resources and modules

Once the conventions modules are in place, your resource definitions become much cleaner. Instead of manually concatenating strings everywhere, you centralize it:

resource "azurerm_resource_group" "this" {
  name     = module.rg_naming.name        # from the RG naming module
  location = local.region

  tags = module.base_tags.tags            # from the tags module
}

For a VNet module you own, you might pass the conventions in as inputs:

module "network" {
  source = "../modules/virtual_network"

  resource_group_name = azurerm_resource_group.this.name
  location            = local.region

  # Use convention-based names/tags for child resources as well
  naming_convention   = module.resource_naming
  base_tags           = module.base_tags.tags
}

When you use the Azure resource modules directly from the azure-terraform-conventions repo (for example virtual_network or azure_firewall), those modules are designed to integrate with the conventions library internally. In other words, you provide high-level metadata (location, environment, workload), and they generate compliant names and tags for you.

Step 5 – Extend and override safely

Sooner or later you’ll hit an edge case: a new region, a special environment (like dr), or a different abbreviation your organization prefers.

Instead of forking the repository, use its design:

  • Extend region / environment maps through overrides (where supported).
  • Pass different prefixes for different resource families (aks, appgw, kv, etc.).
  • Add organization-specific tags via the tags module inputs.

This lets you evolve your naming convention centrally without breaking downstream modules: change it once, run plan, and see the impact across the estate.


Practical decision criteria for each naming component

To make this concrete, here’s how you can think about each piece of the name when configuring the conventions modules.

Prefix (resource family)

  • Keep it short (2–5 characters) and consistent: rg, vnet, aks, fw, kv, sql.
  • Use prefixes that your engineers already recognize from Azure documentation when possible.
  • Avoid mixing styles (don’t use rg- in one place and resgrp- elsewhere).

Region

  • Always derive the region code from the actual Azure region (e.g. westeuropeweu). The conventions/modules/common module is built to help with precisely this.
  • Don’t let teams invent their own ad-hoc abbreviations per project.

Environment

  • Decide upfront: do you want prod, prd, or p? Then stick to it across all subscriptions and tenants.
  • Distinguish “shared” infra from app-specific environments (for example, a shared “platform” environment that still hosts prod workloads).

Namespace / workload

  • Think in terms of business or platform domains: payments, identity, platform, shared.
  • Use this consistently in names and tags so you can filter resources by domain easily.
  • Avoid putting team names directly in the name; use tags for that (teams get renamed; domains last longer).

Purpose / role

  • Use this to distinguish resources inside the same workload: shared, app, data, ingress, etc.
  • Think about how SRE and security folks will search: fw-weu-platform-prod-hub is much more helpful than fw-weu-prod-01.

Instance / sequence

  • Plan for scale from day one. Even if you start at 01, having that slot in the pattern makes blue/green and canary deployments easier later.
  • Use zero-padded numbers (01, 02) so sort order in logs and scripts is predictable.

Tags

Finally, invest a bit of time in your tag taxonomy. The conventions/modules/tags module is meant to take a basic metadata object and generate a consistent set of tags for you. Common choices:

  • Environment
  • Application or Workload
  • Owner (team or group)
  • CostCenter
  • DataClassification (Public / Internal / Confidential / Restricted)
  • ComplianceScope (PCI, HIPAA, etc., where relevant)

Tags are what your finance and security people will mainly live on. By generating them centrally, you avoid the usual “tag drift” where every project invents slightly different spellings.


End-to-end example: a payments AKS platform

Putting it all together, imagine you adopt the following convention:

  • Prefix: rg, vnet, aks, fw
  • Region code: weu for westeurope
  • Namespace: payments
  • Environment: prod
  • Instance: 01

Your resources might look like:

  • rg-weu-payments-prod – resource group for the payments platform in prod.
  • vnet-weu-payments-prod-shared – shared VNet for the platform.
  • aks-weu-payments-prod-01 – first AKS cluster.
  • fw-weu-payments-prod-hub – Azure Firewall in the hub VNet.

In Terraform, most of this naming logic would live in a handful of convention modules from the repo, and your “real” modules would consume the resulting names and tags instead of hardcoding strings. That’s exactly what azure-terraform-conventions is optimized for.


Benefits of this approach (beyond “pretty names”)

  • Scalability: Dozens of teams can ship Terraform independently and still produce a coherent Azure estate.
  • Security & DevSecOps: Policy definitions, RBAC rules, and incident response scripts can rely on predictable names and tags rather than regex circus acts.
  • Governance & compliance: Internal and external standards are easier to enforce when naming and tagging rules are centralized and tested as code.
  • Developer experience: Engineers pass a relatively small “naming/metadata” object into modules and get compliant names and tags back for free.
  • Maintainability: When Azure changes or your org renames environments, you update the mapping in one place, run plan, and see the effect everywhere.

How to adopt these conventions in your organization

  • Start with a pilot: Pick one workload (or a shared platform stack) and wire it up to the conventions modules in this repo.
  • Bake it into templates: Update your “new Terraform service” template so it always includes calls to the naming and tagging modules.
  • Customize via inputs, not forks: Use overrides and structured inputs to adapt the modules to your org, rather than copying and diverging the code.
  • Align with security and finance: Agree on tag keys and naming components together, then codify them in the conventions modules.
  • Iterate safely: Treat naming like any other architecture decision—review, evolve, and test via pull requests and plans.

If you want a concrete starting point, clone the repo, open the README.md, and then inspect the conventions/modules folder. Start with the tags and resource_group modules for one stack, and let the pattern grow from there.

Repo link again: github.com/SalehElnagar/azure-terraform-conventions

Once your naming strategy is encoded in these modules, every new Terraform project automatically speaks the same language. That’s when Azure starts to feel like a well-designed platform instead of a random collection of resources.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top