Terraform 44: Capstone — a multi-env service module
easy⏱ 5 mincourseterraform
The module: RG + KV + Service Bus + tags
The service module takes environment, service, location, topics, and extra_tags, and produces the whole per-service footprint — names and tags computed once, topics fanned out with for_each (a toset).
# modules/service/main.tf
locals {
tags = merge({
ManagedBy = "terraform"
Environment = var.environment
Service = var.service
}, var.extra_tags)
}
resource "azurerm_resource_group" "this" {
name = "${var.environment}-${var.service}-rg"
location = var.location
tags = local.tags
}
resource "azurerm_key_vault" "this" {
name = "${var.service}-kv-${var.environment}"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
tenant_id = var.tenant_id
sku_name = "standard"
tags = local.tags
}
resource "azurerm_servicebus_namespace" "this" {
name = "${var.environment}-${var.service}-namespace"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
sku = "Standard"
tags = local.tags
}
resource "azurerm_servicebus_topic" "this" {
for_each = toset(var.topics)
name = each.value
namespace_id = azurerm_servicebus_namespace.this.id
}
Each environment is one module call
The root config calls the same module three times with per-env inputs. Keep those inputs in a locals map and drive the calls with for_each so dev/uat/prod stay in lockstep — one change to the module updates all three.
# environments/main.tf
locals {
environments = {
dev = { location = "westus2", topics = ["orders-events"] }
uat = { location = "westus2", topics = ["orders-events"] }
prod = { location = "eastus2", topics = ["orders-events", "orders-audit"] }
}
}
module "service" {
source = "../modules/service"
for_each = local.environments
environment = each.key
service = "orders"
location = each.value.location
topics = each.value.topics
tenant_id = var.tenant_id
}
Isolate state per environment
Even with one module, keep separate state per env (distinct backend key, e.g. orders/dev.tfstate vs orders/prod.tfstate, or separate workspaces). A dev apply must never be able to touch prod's state. Promote a change dev -> uat -> prod through the pipeline, not by editing prod directly.
prod is not just dev with more replicas
Per-env inputs are where real differences live: prod might use a different location, extra topics, a higher SKU, or purge protection on. Keeping them as data in the environments map — not forked code — is what makes the module trustworthy across tiers.
Compute the full resource set
Given the environments config, write resourcesFor(env, cfg) returning the resource names one module call creates: `${env}-orders-rg`, `orders-kv-${env}`, `${env}-orders-namespace`, plus one `${env}-orders-topic-${t}` per topic. Log the total count across all three environments — with dev(1)+uat(1)+prod(2) topics it must be 13.