PF-01 · AWS · IaC
AWS multi-region Terraform
A Terraform module set that builds the same infrastructure in 16 AWS regions from one locals map per region.
- Role
- Design and implementation
- Period
- Personal project · released Nov 2026
- Cloud
- AWS
- Scale
- 16 regions · 24 modules · 3 examples
- Key tech
- Terraform · AWS · EKS · CloudWatch · AWS Chatbot
- 16
- Regional provider aliases
- 24
- Terraform modules
- 3
- Validated examples
At a glance
Problem
The same resources had to exist in many regions. More regions meant more copied code and a higher risk that regions drift apart.
Approach
A locals map per region declares the wanted state, and module plus for_each creates it all. Each region has a provider alias that assumes the same role. Terraform cannot choose a provider dynamically inside for_each, so the module block repeats per region.
Result
A change is one map entry, and stable keys mean reordering never recreates anything. The 24 modules carry defaults for encryption, IMDSv2, and private S3, and three examples cover networks, EKS, and alerting.
Up close
A locals map per region
What this code does: Declares the wanted network state per region as a map. The outer key is the region (seoul), the inner key is the VPC name (demo-seoul), and the value holds the VPC CIDR and its subnet list (name, CIDR, availability zone, and whether public IPs are auto-assigned). Adding a region or a VPC is one more entry in this map; the code that creates resources does not change.
# Declare the wanted state per region. Adding a region or a VPC is one more map entry.
locals {
networks = {
seoul = {
demo-seoul = {
cidr_block = "10.0.0.0/16"
subnets = [
{ subnet_name = "demo-seoul-a", subnet_cidr = "10.0.1.0/24", ipv6_cidr_block = null, availability_zone = "ap-northeast-2a", autoassign_public = "false", tags = {} },
{ subnet_name = "demo-seoul-c", subnet_cidr = "10.0.2.0/24", ipv6_cidr_block = null, availability_zone = "ap-northeast-2c", autoassign_public = "false", tags = {} },
]
}
}
A module with for_each reads the map
What this code does: Runs the VPC module once for each entry in the Seoul map (local.networks.seoul). The map key (each.key) becomes the VPC name, and the CIDR and subnet list come from the value (each.value). The providers line binds the Seoul alias (aws.seoul), so everything this block creates lands in Seoul, and because the key is the resource address, reordering the map never recreates an existing VPC.
module "seoul_vpc" {
source = "../../modules/vpc"
for_each = local.networks.seoul
providers = { aws = aws.seoul }
vpc_name = each.key
cidr_block = each.value.cidr_block
instance_tenancy = "default"
use_ipv6_cidr_block = false
subnets = each.value.subnets
}What I learned
Stable keys mattered as much as less repetition. count identifies resources by their position in a list, so inserting or reordering an entry can recreate everything after it, while for_each identifies them by map key, so the same resource is addressed whatever the order. Terraform cannot pick a provider inside for_each, and instead of hiding that limit, I repeated the module block per region in plain sight, which turned out easier to read and change. Since then, when the same structure has to exist in many places, I separate data (the map) from code (the module) first, and I judge the design by whether a change ends at one map entry.