Terraform 37: Terraform Modules 5: The shared common-modules repo
easy⏱ 5 mincourseterraform
One repo, many reusable modules
A terraform-common repo groups modules by provider/topic (azure/tags, azure/monitoring, azure/key-vault). Services consume them by git source with a pinned ?ref=, so a fix to tags rolls out only when each service bumps its ref — controlled, reviewable propagation.
# a shared tags module lives at terraform-common//azure/tags
module "tags" {
source = "git::https://git.example.com/myorg/terraform-common.git//azure/tags?ref=v1.4.0"
# per-service overrides on top of the org defaults
tags = {
Service = "orders"
Owner = "orders-team@myorg.example"
}
}
# every resource then reuses the merged set
resource "azurerm_resource_group" "rg" {
name = "myorg-orders-dev-rg"
location = "westus2"
tags = module.tags.tags
}
The module owns the standard; callers only override
Inside the tags module, merge(local.base_tags, var.tags) layers caller overrides on top of org defaults. The base set (ManagedBy = terraform, department, cost center) is defined once, centrally — callers pass only what's specific to them.
# terraform-common//azure/tags/main.tf
locals {
base_tags = {
ManagedBy = "terraform"
Department = "platform"
CostCenter = "cc-1000"
}
tags = merge(local.base_tags, var.tags)
}
output "tags" {
value = local.tags
}
Version the common repo like a library
Treat terraform-common as a published library: tag releases (v1.4.0), keep a changelog, and never force-push a tag. A consumer pinned to v1.4.0 must get byte-identical module code forever — that's what makes plans reproducible across teams.
Implement the tag merge
Mirror the module's merge(base_tags, overrides): write buildTags(overrides) that starts from { ManagedBy: "terraform", Department: "platform" } and lets overrides win on conflicts. Log JSON.stringify(buildTags({ Service: "orders", Department: "payments" })) — ManagedBy stays terraform, Department becomes payments.