Terraform for_each: Map-Driven Resource Creation
Scenario
Your organisation needs to create multiple IAM users for a new development team, each with different access levels. You also need to create multiple S3 buckets for different business units with different configurations. Instead of hardcoding individual resource blocks, you want to drive everything from a map variable so any future additions only require a change to the variable.
- Use for_each = toset(list) to create IAM users from a list of usernames.
- Use for_each = map to create S3 buckets with per-bucket configurations (versioning on/off, tags).
- Use each.key and each.value to access the current iteration's key and value.
- Use a for expression in an output to build a map of username → IAM ARN.
- Demonstrate why removing one user from the map does not affect others.
Additional Context
for_each with a map gives you control over the state key. Each resource is stored as aws_s3_bucket.buckets["analytics"] instead of aws_s3_bucket.buckets[0]. This means deleting one key from the map only destroys that specific resource — not all subsequent ones.
Use toset() when iterating over a list with for_each — it converts the list to a set, which eliminates duplicates and provides stable keys based on the value itself (not its index).
versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}variables.tf
variable "aws_region" { type = string; default = "us-east-1" }
# A simple list — for IAM users where all users are treated identically.
variable "dev_team_users" {
description = "List of IAM usernames for the development team."
type = list(string)
default = ["alice", "bob", "carol"]
}
# A map — for S3 buckets where each bucket has different settings.
variable "team_buckets" {
description = "Map of S3 bucket logical names to their configurations."
type = map(object({
versioning = bool
team = string
}))
default = {
"analytics" = { versioning = true, team = "data" }
"frontend" = { versioning = false, team = "web" }
"backups" = { versioning = true, team = "ops" }
}
}main.tf
# main.tf
provider "aws" {
region = var.aws_region
}
data "aws_caller_identity" "current" {}
# for_each with toset(list):
# each.key = each.value = the username string
# State addresses: aws_iam_user.dev["alice"], aws_iam_user.dev["bob"], etc.
# Removing "bob" from the list only destroys aws_iam_user.dev["bob"].
resource "aws_iam_user" "dev" {
for_each = toset(var.dev_team_users)
name = each.key # the username
path = "/dev-team/"
tags = { Team = "dev", ManagedBy = "terraform" }
}
# for_each with a map:
# each.key = the map key (e.g., "analytics")
# each.value = the object value (e.g., { versioning = true, team = "data" })
resource "aws_s3_bucket" "team_buckets" {
for_each = var.team_buckets
bucket = "${each.key}-bucket-${data.aws_caller_identity.current.account_id}"
tags = {
Name = "${each.key}-bucket"
Team = each.value.team
ManagedBy = "terraform"
}
}
# Conditionally enable versioning based on the map value.
# for_each is used again — one versioning resource per bucket.
resource "aws_s3_bucket_versioning" "team_buckets" {
for_each = var.team_buckets
bucket = aws_s3_bucket.team_buckets[each.key].id
versioning_configuration {
# Use each.value to toggle versioning per bucket.
status = each.value.versioning ? "Enabled" : "Suspended"
}
}outputs.tf
# for expression: transform a map-keyed resource collection into a new map.
# Result: { "alice" = "arn:aws:iam::...:user/dev-team/alice", ... }
output "dev_user_arns" {
description = "Map of username to IAM ARN for all dev team users."
value = { for username, user in aws_iam_user.dev : username => user.arn }
}
output "bucket_names" {
description = "Map of logical bucket name to actual S3 bucket name."
value = { for k, v in aws_s3_bucket.team_buckets : k => v.id }
}Workflow Commands
terraform init
terraform apply
# View state addresses — note the map keys, not numeric indices
terraform state list
# Output the user ARN map
terraform output -json dev_user_arns
# Simulate removing a user — only that user's resources are destroyed
# (edit variables.tf, remove "bob", then:)
terraform plan # shows: 1 to destroy (aws_iam_user.dev["bob"])
terraform destroyApply complete! Resources: 9 added, 0 changed, 0 destroyed.
Outputs:
bucket_names = {
"analytics" = "analytics-bucket-123456789012"
"backups" = "backups-bucket-123456789012"
"frontend" = "frontend-bucket-123456789012"
}
dev_user_arns = {
"alice" = "arn:aws:iam::123456789012:user/dev-team/alice"
"bob" = "arn:aws:iam::123456789012:user/dev-team/bob"
"carol" = "arn:aws:iam::123456789012:user/dev-team/carol"
}
