Devops

Mastering Terraform for_each: Dynamic Infrastructure Without Repetition

April 7, 2026
Published
#Automation#Cloud#DevOps#Infrastructure as Code#Terraform

There’s a moment in every Terraform project where copy-paste starts to feel wrong. You’re defining the same resource over and over—just with slightly different names or values. That’s usually your cue to reach for for_each.

Terraform’s for_each lets you define multiple instances of a resource using a map or set, giving you better control and readability compared to older approaches like count. If you’ve ever struggled with indexing or resource drift, this is where things start to click.

Why for_each exists (and why count often isn’t enough)

Before for_each, most developers relied on count. It works, but it comes with a catch: resources are tracked by index. Change the order of items, and Terraform might destroy and recreate resources unexpectedly.

Here’s where for_each stands out:

  • Resources are identified by keys, not indexes
  • Safer updates when data changes
  • More readable configurations

Instead of saying “create 3 resources,” you’re saying “create resources based on these named items.” That shift matters.

A quick example: creating multiple AWS S3 buckets

Let’s start with something simple. Imagine you want to create multiple S3 buckets with different names.

TEXT
1variable "buckets" {
2  default = {
3    logs  = "app-logs-bucket"
4    media = "app-media-bucket"
5    backup = "app-backup-bucket"
6  }
7}
8
9resource "aws_s3_bucket" "buckets" {
10  for_each = var.buckets
11
12  bucket = each.value
13
14  tags = {
15    Name = each.key
16  }
17}

Here’s what’s happening:

  • for_each iterates over a map
  • each.key refers to "logs", "media", etc.
  • each.value is the actual bucket name

Terraform will create three distinct resources, each tied to its key. No indexing confusion.

Understanding each.key vs each.value

This is one of the most important parts to internalize.

  • each.key: The identifier (used in state tracking)
  • each.value: The actual data you assigned

If you’re using a set instead of a map, then each.key and each.value will be the same.

Example with a set

TEXT
1variable "regions" {
2  default = ["us-east-1", "us-west-1"]
3}
4
5resource "aws_vpc" "example" {
6  for_each = toset(var.regions)
7
8  cidr_block = "10.0.0.0/16"
9
10  tags = {
11    Region = each.value
12  }
13}

Where for_each really shines

This feature becomes especially powerful when your infrastructure depends on structured data.

Let’s say you’re defining multiple EC2 instances with different configurations:

TEXT
1variable "instances" {
2  default = {
3    web = {
4      instance_type = "t3.micro"
5    }
6    api = {
7      instance_type = "t3.small"
8    }
9  }
10}
11
12resource "aws_instance" "servers" {
13  for_each = var.instances
14
15  ami           = "ami-123456"
16  instance_type = each.value.instance_type
17
18  tags = {
19    Name = each.key
20  }
21}

Now your infrastructure is data-driven. Add a new server? Just update the map.

A common mistake developers make

Mixing count and for_each in the same context—or trying to switch between them mid-project—can cause unexpected resource recreation.

Terraform treats count-based and for_each-based resources as fundamentally different. Switching between them often forces destruction and recreation.

If you’re starting fresh, prefer for_each unless you explicitly need indexed behavior.

Referencing resources created with for_each

This is where syntax changes slightly compared to single resources.

Instead of:

TEXT
1aws_instance.example.id

You’ll use:

TEXT
1aws_instance.servers["web"].id

The key becomes part of the reference path, which keeps everything explicit and predictable.

Conditional resource creation with for_each

You can combine for_each with filtering logic to control what gets created.

JAVASCRIPT
1locals {
2  filtered_instances = {
3    for key, value in var.instances : key => value
4    if value.instance_type == "t3.micro"
5  }
6}
7
8resource "aws_instance" "filtered" {
9  for_each = local.filtered_instances
10
11  ami           = "ami-123456"
12  instance_type = each.value.instance_type
13}

This pattern is incredibly useful when dealing with large configurations or feature flags.

for_each vs count: a quick mental model

  • Use count when resources are identical and order matters
  • Use for_each when resources are distinct and named

In most real-world Terraform setups, for_each ends up being the safer and more maintainable choice.

Performance and state considerations

Using for_each doesn’t significantly impact performance, but it does improve state clarity. Each resource is tracked by a stable key, which reduces unintended changes during plan/apply cycles.

However, changing keys in your map will cause Terraform to destroy and recreate those resources. Treat keys as stable identifiers.

When not to use for_each

Despite its flexibility, there are cases where it’s overkill:

  • When creating a fixed number of identical resources
  • When you don’t need unique identifiers
  • When data structure complexity outweighs benefits

Sometimes simplicity wins.

Wrapping it up

Terraform for_each isn’t just about looping—it’s about modeling infrastructure in a way that’s stable, readable, and scalable. Once you start using it with maps and structured variables, your configurations become easier to extend and far less error-prone.

If your Terraform code still relies heavily on repetition, introducing for_each is one of the quickest ways to clean it up and make it production-ready.

Comments

Leave a comment on this article with your name, email, and message.

Loading comments...

Similar Articles

More posts from the same category you may want to read next.

Share: