← Back to list

Structuring Azure Infrastructure with Terraform — A Modular, Environment-Aware Approach

Infrastructure teams often face the same dilemma: start simple and refactor later, or invest in structure upfront and move slower at first…

Jagdish Wagh · 2026-05-30 15:46 · 0 claps · 3.2 min read
#azure-infrastructure #terraform #infra-as-code #hashicorp
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud 🥊 · Combat Sports

Structuring Azure Infrastructure with Terraform — A Modular, Environment-Aware Approach

Infrastructure teams often face the same dilemma: start simple and refactor later, or invest in structure upfront and move slower at first. After managing Azure resources through a series of brittle, monolithic Terraform configurations, we chose the latter — and it paid off.

In this post, we walk through the directory layout, provider configuration, remote state setup, and module design we use to manage our Data platform across development and production environments on Azure. The patterns are straightforward, but the reasoning behind each choice matters.

Why structure matters in Terraform

Terraform makes it easy to get started, and equally easy to end up with a 2,000-line main.tf that no one dares touch. Shared state, environment-specific drift, and tangled provider configs are the natural enemies of scale.

Our solution is a layered folder structure where environments are isolated, shared logic lives in modules, and variables enforce discipline at every boundary.

The directory layout

Everything lives under a top-level terraform/ directory. Each environment gets its own folder; reusable logic goes into shared modules.

terraform/ ├── shared/ │ └── modules/ │ ├── dev/ │ ├── main.tf │ ├── variables.tf │ └── dev.tfvars │ ├── prod/ │ ├── main.tf │ ├── variables.tf │ └── prod.tfvars

Key Design Principles

  • Reusability → Common logic is placed in shared/modules
  • Environment isolation → Each environment has its own configuration
  • Scalability → Easy to extend for new regions or teams
  • Governance → Standardized tagging and naming conventions

Terraform Module Setup

To ensure reusability and consistency, we organize our infrastructure using modular Terraform design. All reusable components are placed under the shared/modules directory, allowing multiple teams and environments to use the same standardized logic.

Each module is designed to represent a specific resource or service (e.g., Resource Group, Storage, Key Vault), making the infrastructure easy to scale and maintain.

Modelule Structure

Module Structure

Module Structure

Resource Group Module

resource "azurerm_resource_group" "resource_group" {
 name = var.resource_group_name
 location = var.location
 tags = var.tags
}

Configuring providers and remote state

The main.tf in each environment handles three responsibilities: pinning provider versions, configuring the Azure backend, and wiring up module calls. Here is the dev configuration:

terraform {
  required_providers = {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.42"
    }
    databricks = {
      source  = "databricks/databricks"
      version = "~> 1.0"
    }
    azuread = {
      source  = "hashicorp/azuread"
      version = "~> 2.0"
    }
  }

  backend "azurerm" {
    resource_group_name  = var.resource_group_name
    storage_account_name = var.storage_account_name
    container_name       = var.container_name
    key                  = var.file_path
  }
}

provider "azurerm" {
  features {}
}

provider "azuread" {
  tenant_id = var.tenant_id
}

Notice that the backend uses variables rather than hardcoded values. This is intentional — it lets each environment point to its own Azure Storage container without changing the code, only the *.tfvars file.

Why pin provider versions? Terraform providers release frequently, and minor bumps can introduce breaking changes. Using version constraints like ~> 3.42 allows patch updates while guarding against unexpected major changes in shared pipelines.

Calling shared modules — the security resource group

With providers and backend in place, the real work begins: calling modules. Here is how we provision the security resource group in the dev environment, applying a rich set of governance tags through Terraform’s merge() function:

dev/main.tf — security resource group moduleHCL

module "resource_group_security" {
  source = "./shared/modules/resource_group"

  resource_group_name = "rg-dp-security-${var.environment}-weu-01"
  location            = var.location

  tags = merge(var.tags, {
    ApplicationName  = "DataPlatform-AI"
    BusinessUnit     = "DataPlatform"
    CostCenter       = "DL-XX-001"
    WorkType         = "INFRA"
    ServiceCategory  = "Security"
    BillingOwner     = "DataEngineering"
    Confidentiality  = "Internal"
    Criticality      = "High"
    CreatedBy        = "Terraform"
    ExpirationDate   = "None"
    Description      = "Security resources: NSG, Private Endpoints, Routing"
  })
}

The naming convention rg-dp-security-${var.environment}-weu-01 dynamically resolves to rg-dp-security-dev-weu-01 in development and rg-dp-security-prod-weu-01 in production — with identical module code.

Why Tagging Matters

In a multi-team and multi-environment setup, tagging is not just metadata — it is critical for:

  • Cost tracking and chargeback
  • Resource ownership identification
  • Security and compliance classification
  • Operational governance

Terraform Exuection Commond

1terraform init — wire up providers and backend

Downloads providers, initialises the remote backend, and resolves module sources. Run this once per environment, and again whenever providers or modules change.

2 terraform plan — inspect changes before they happen

Compares your configuration against real Azure state and produces a diff. Nothing changes in Azure — this is read-only. Always save the plan output for apply.

3terraform apply — execute with explicit approval Applies the


메타데이터
post_id
c8de80c0ec35
slug
structuring-azure-infrastructure-with-terraform-a-modular-environment-aware-approach-c8de80c0ec35
url
https://medium.com/@jagdishmwagh/structuring-azure-infrastructure-with-terraform-a-modular-environment-aware-approach-c8de80c0ec35
canonical_url
https://medium.com/@jagdishmwagh/structuring-azure-infrastructure-with-terraform-a-modular-environment-aware-approach-c8de80c0ec35
author_url
https://medium.com/@jagdishmwagh
status
ok
fetched_at
2026-09-05 03:25:13