Azure Management

Terraform: Easier Azure Region Pairing and Governance with the Build5Nines Region Map Module

Managing Azure regions in Terraform sounds simple at first. You define a location variable, pass in something like eastus, and move on.

That works fine for a small deployment.

But once you start building real enterprise-scale Azure environments, region management gets messy fast. You need consistent region abbreviations for naming standards. You need to know the paired Azure region for disaster recovery. You may need to restrict deployments to approved corporate regions. You may also need to understand whether your primary and secondary regions cross geographies or broader compliance zones.

I built the Build5Nines Terraform Azure Region Map module to help solve this problem.

This module provides a streamlined, enterprise-ready way to manage Azure region metadata, paired regions, short names, allowed-region checks, and DR-related region decisions directly in Terraform. It is designed for DevOps Engineers, SREs, Cloud Engineers, and platform teams that want to standardize Azure region handling across Infrastructure as Code projects.

The module is published in the Terraform Registry as:

Build5Nines/region-map/azure

In this article, I’ll walk through how to get started using it, beginning with a simple example and then moving into a few more practical enterprise scenarios.

Why Azure Region Management Gets Complicated

Most Terraform projects start with a variable like this:

variable "location" {
  type    = string
  default = "eastus"
}

Then that value gets passed into Azure resources:

resource "azurerm_resource_group" "main" {
  name     = "rg-myapp-prd"
  location = var.location
}

This works, but it does not answer a few important questions:

What short code should be used for the region in resource names?

What is the paired Azure region for disaster recovery?

Is this region approved for use by the organization?

Is the secondary region in the same geography?

Does the DR design cross a compliance zone?

Should the input support both eastus and East US?

These are the kinds of details that often end up duplicated across Terraform projects, landing zone modules, naming modules, CI/CD checks, and internal documentation.

The goal of this module is to centralize that logic so each Terraform project does not need to reinvent it.

Basic Usage

To get started, add the module to your Terraform configuration:

module "regions" {
  source  = "Build5Nines/region-map/azure"
  version = "~> 1.0.0"

  primary_region = "eastus"
}

That’s it for the most basic usage.

The module will normalize the region input and return useful metadata about the primary region, secondary region, paired region, geography, compliance zone, and region abbreviation.

For example, you can use the module outputs when creating Azure resource groups:

resource "azurerm_resource_group" "primary" {
  name     = "rg-myapp-${module.regions.primary.short}-prd"
  location = module.regions.primary.name
}

resource "azurerm_resource_group" "secondary" {
  name     = "rg-myapp-${module.regions.secondary.short}-prd"
  location = module.regions.secondary.name
}

If the primary region is eastus, the module can return a short name such as eus, and automatically resolve the Azure paired region as westus.

This makes it much easier to keep resource naming consistent across primary and secondary deployments.

Open Source: The Build5Nines/region-map/azure module is open source using the MIT License. You can view the source code for it over at: https://github.com/Build5Nines/terraform-azure-region-map

Reviewing the Module Outputs

The module exposes a few useful outputs you can use throughout your Terraform configuration.

For the primary region, you get a region detail object:

module.regions.primary

Example:

{
  name            = "eastus"
  short           = "eus"
  display_name    = "East US"
  geography       = "United States"
  compliance_zone = "Americas"
}

The secondary region follows the same object structure:

module.regions.secondary

Example:

{
  name            = "westus"
  short           = "wus"
  display_name    = "West US"
  geography       = "United States"
  compliance_zone = "Americas"
}

You also get additional outputs that can be useful in deployment logic:

module.regions.paired_region
module.regions.strategy
module.regions.is_allowed
module.regions.is_secondary_allowed
module.regions.is_cross_geography
module.regions.is_cross_zone

These outputs allow you to make decisions in Terraform based on region policy, DR topology, and enterprise standards.

Region Input Normalization

One of the small but useful features of this module is region input normalization.

Azure region names are commonly written in different ways depending on where they come from. For example:

primary_region = "eastus"

And:

primary_region = "East US"

Both refer to the same Azure region.

The module normalizes region inputs by converting them to lowercase and removing spaces. This means teams can pass in either canonical Azure region names or human-readable display names.

This is especially helpful when region values come from different sources such as:

variable "primary_region" {
  type = string
}

Or from CI/CD pipeline parameters:

primary_region = var.pipeline_region

You can accept friendly input values while still keeping your Terraform logic consistent.

Advanced Example: Enforcing Allowed Azure Regions

One of the primary reasons I built this module was to help with enterprise-scale Azure governance.

In many organizations, not every Azure region is approved for deployment. There may be legal, compliance, latency, supportability, or cost-management reasons to limit where workloads can run.

With this module, you can pass in an approved list of Azure regions:

variable "approved_regions" {
  type = list(string)

  default = [
    "eastus",
    "westus",
    "centralus"
  ]
}

Then use that list with the module:

module "regions" {
  source  = "Build5Nines/region-map/azure"
  version = "~> 1.0.0"

  primary_region  = var.primary_region
  allowed_regions = var.approved_regions
}

The module returns whether the primary and secondary regions are allowed:

module.regions.is_allowed
module.regions.is_secondary_allowed

You can use this in a policy-style check:

resource "null_resource" "region_policy_check" {
  count = module.regions.is_allowed ? 0 : 1

  provisioner "local-exec" {
    command = "echo 'Region ${var.primary_region} is not approved for deployment.' && exit 1"
  }
}

This gives you a simple way to enforce region rules inside your Terraform workflow.

For example, if someone tries to deploy to a region that is not in the approved list, the Terraform deployment can fail before resources are provisioned.

This is not meant to replace Azure Policy, but it is a useful guardrail in Infrastructure as Code. It helps catch mistakes earlier in the deployment lifecycle.

Advanced Example: Using Azure Paired Regions for DR

Azure paired regions are important when designing disaster recovery strategies. However, manually maintaining region pair mappings across multiple Terraform projects can be tedious and error-prone.

With this module, the default strategy is:

strategy = "paired-region"

That means if you configure a primary region, the module will automatically identify the paired Azure region.

Example:

module "dr_regions" {
  source  = "Build5Nines/region-map/azure"
  version = "~> 1.0.0"

  primary_region = "uksouth"
}

You can then use the secondary region output in your DR resources:

resource "azurerm_resource_group" "primary" {
  name     = "rg-${var.app_name}-${module.dr_regions.primary.short}-prd"
  location = module.dr_regions.primary.name
}

resource "azurerm_resource_group" "secondary" {
  name     = "rg-${var.app_name}-${module.dr_regions.secondary.short}-prd"
  location = module.dr_regions.secondary.name
}

This allows your Terraform code to stay focused on intent.

Instead of hard-coding the secondary region everywhere, you define the primary region and let the module resolve the paired region metadata.

You can also inspect whether the primary and secondary regions cross geographies or compliance zones:

module.dr_regions.is_cross_geography
module.dr_regions.is_cross_zone

This is helpful for SRE and platform teams that need to evaluate DR designs against data residency requirements.

Advanced Example: Custom Secondary Region Strategy

Sometimes the Azure paired region is not the region you want to use.

Your organization may have a specific DR architecture, latency requirement, data residency requirement, or platform standard that points to a different secondary region.

In that case, you can use the custom strategy:

module "regions" {
  source  = "Build5Nines/region-map/azure"
  version = "~> 1.0.0"

  primary_region   = "eastus"
  secondary_region = "North Europe"
  strategy         = "custom"
}

When using the custom strategy, the module uses the value you provide in secondary_region.

The module still exposes the Azure paired region as a reference through:

module.regions.paired_region

This is useful because you can choose a custom DR region while still knowing what the official Azure paired region would have been.

You can also evaluate the custom pairing:

module.regions.is_cross_geography
module.regions.is_cross_zone

For example, if your primary region is in the United States and your secondary region is in Europe, those flags can help identify that the deployment crosses both geography and compliance-zone boundaries.

Advanced Example: Standardizing Azure Resource Names

A common enterprise Terraform pattern is to include a region abbreviation in resource names.

For example:

rg-myapp-eus-prd
rg-myapp-wus-prd

The problem is that every organization seems to maintain these abbreviations differently. Some teams use eus, others use eastus, and others create custom naming rules.

This module includes built-in abbreviations for Azure regions and also supports overrides.

Here is a simple naming example:

module "regions" {
  source  = "Build5Nines/region-map/azure"
  version = "~> 1.0.0"

  primary_region = var.primary_region
}

locals {
  primary_resource_group_name   = "rg-${var.app_name}-${module.regions.primary.short}-${var.environment}"
  secondary_resource_group_name = "rg-${var.app_name}-${module.regions.secondary.short}-${var.environment}"
}

You can then use those locals in your resources:

resource "azurerm_resource_group" "primary" {
  name     = local.primary_resource_group_name
  location = module.regions.primary.name
}

resource "azurerm_resource_group" "secondary" {
  name     = local.secondary_resource_group_name
  location = module.regions.secondary.name
}

If your organization has its own naming standard, you can override abbreviations:

module "regions" {
  source  = "Build5Nines/region-map/azure"
  version = "~> 1.0.0"

  primary_region = "eastus"

  region_abbreviations = {
    "eastus" = "east"
    "westus" = "west"
  }
}

Now the module will use your custom abbreviation values instead of the built-in defaults.

This makes the module flexible enough to support both greenfield projects and existing enterprise naming standards.

Putting It All Together

Here is a more complete example that combines region normalization, allowed regions, paired-region lookup, and naming conventions:

variable "app_name" {
  type    = string
  default = "myapp"
}

variable "environment" {
  type    = string
  default = "prd"
}

variable "primary_region" {
  type    = string
  default = "East US"
}

variable "approved_regions" {
  type = list(string)

  default = [
    "eastus",
    "westus",
    "centralus"
  ]
}

module "regions" {
  source  = "Build5Nines/region-map/azure"
  version = "~> 1.0.0"

  primary_region  = var.primary_region
  allowed_regions = var.approved_regions
}

locals {
  primary_rg_name   = "rg-${var.app_name}-${module.regions.primary.short}-${var.environment}"
  secondary_rg_name = "rg-${var.app_name}-${module.regions.secondary.short}-${var.environment}"
}

resource "null_resource" "primary_region_policy_check" {
  count = module.regions.is_allowed ? 0 : 1

  provisioner "local-exec" {
    command = "echo 'Primary region ${var.primary_region} is not approved.' && exit 1"
  }
}

resource "null_resource" "secondary_region_policy_check" {
  count = module.regions.is_secondary_allowed ? 0 : 1

  provisioner "local-exec" {
    command = "echo 'Secondary region ${module.regions.secondary.name} is not approved.' && exit 1"
  }
}

resource "azurerm_resource_group" "primary" {
  name     = local.primary_rg_name
  location = module.regions.primary.name
}

resource "azurerm_resource_group" "secondary" {
  name     = local.secondary_rg_name
  location = module.regions.secondary.name
}

With this approach, the Terraform code is doing more than just deploying resources. It is also applying region standards, improving naming consistency, identifying the secondary region, and validating whether the selected regions are approved.

Why This Helps at Enterprise Scale

At small scale, region logic is easy to manage manually.

At enterprise scale, it becomes part of the platform.

DevOps Engineers and SREs need consistent patterns that work across many teams, applications, environments, and deployment pipelines. When each workload team implements region logic differently, the result is inconsistency and unnecessary complexity.

This module helps centralize a few common concerns:

Region normalization

Azure paired-region lookup

Consistent naming abbreviations

Allowed-region checks

DR topology awareness

Geography and compliance-zone metadata

The goal is to make Terraform projects easier to standardize without requiring every team to build and maintain the same region-mapping logic themselves.

Final Thoughts

I created the Build5Nines Terraform Azure Region Map module because I saw a gap in how Azure region management is commonly handled in HashiCorp Terraform.

There are plenty of modules for deploying infrastructure, but I wanted a simple helper module focused specifically on region decisions for enterprise Azure environments. Region pairs, short names, allowed regions, geography, and compliance-zone awareness are all things that come up repeatedly in real-world infrastructure projects.

Instead of copying region maps and naming logic from one Terraform project to another, this module gives you a reusable way to centralize that logic.

If you are building Azure infrastructure with Terraform and want a cleaner way to manage primary regions, secondary regions, naming standards, and deployment guardrails, give this module a try in your own Terraform projects.

Use it as a simple helper in a single workload, or standardize it across your landing zone and platform modules to make Azure region management more consistent across your organization.

Related Articles

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.