Skip to content
All projects

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
GitHub
16
Regional provider aliases
24
Terraform modules
3
Validated examples

At a glance

16 provider aliases, one assume_roleus-west-2us-west-1us-east-2us-east-1sa-east-1ap-south-1ap-southeast-1ap-southeast-2ap-northeast-3ap-northeast-2ap-northeast-1eu-north-1eu-west-1eu-west-2eu-west-3eu-central-1assume_roleSame role_arn in every region16 regions · 24 modules · one locals map per region
Adding a region means one more locals entry and one more module block.

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

locals map per regionlocals { networks = { seoul = { demo-seoul = { cidr_block = "10.0.0.0/16" subnets = [ ... ] } } }}for_eachVPCcidr_blockSubnetsubnets listSecurity GroupRule map through for_eachEC2 / EKSInstances and node groups per serviceOne more map entry, that many more resources
Keys are stable, so reordering entries recreates nothing.
EKS 1.32Public and private endpoints, allowed ranges onlyingamer7i.xlarge × 5battlem7i.large × 2mergem7i.large × 2r7i.xlarger7i.xlarger7i.xlarger7i.xlarger7i.xlargem7i.largem7i.largem7i.largem7i.largelabels = { "app/role" = service name }
Node labels separate services, so workloads land on their own node group.

A locals map per region

What this code does: Declares the wanted network state per region as a map.

# 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 per map entry and binds the regional provider.

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. With for_each instead of count, reordering entries does not recreate resources.

What I would improve

CI validates the examples but does not apply them. The short variable descriptions in the modules are also worth improving.

Open to work · Cloud infrastructure and security