
If you are writing Terraform configurations for more than one resource, environment, user, subnet, bucket, or module instance, you will eventually run into the same problem: repetition.
You can copy and paste resource blocks, but that gets difficult to maintain. You can use count, but index-based resources can become fragile when items are added, removed, or reordered. This is where terraform for each becomes one of the most useful tools in the Terraform language.
The for_each meta-argument lets teams create multiple resource or module instances from a map or set, while keeping each instance tied to a stable key. That makes Terraform code cleaner, easier to review, and safer to scale across environments.
This guide explains how for_each works, when to use it instead of count, how to use it with maps and sets, how to work around list limitations, how to use for_each with modules, and how to avoid common errors.
What Is Terraform for_each?
Terraform for_each is a meta-argument that allows a resource, module, or supported block to create one instance for each item in a map or set of strings. Each item becomes a separate Terraform-managed instance with its own address, lifecycle, and state entry. HashiCorp’s Terraform documentation explains that for_each accepts a map or set of strings and creates an instance for each item in that collection.
The real value of for_each is not just shorter code. It gives each resource instance a meaningful key, which makes Terraform plans and state easier to understand.
Why Infrastructure Teams Use for_each
Infrastructure teams use for_each because cloud environments are rarely made of one resource.
A platform team may need to create multiple subnets across availability zones. A security team may need multiple IAM roles. An application team may need one storage bucket per service. A DevOps team may need the same module deployed across development, staging, and production.
Without for_each, teams often duplicate resource blocks or rely on count. Both approaches can work, but they become harder to manage as configurations grow.
for_each helps solve that by making repeated infrastructure more intentional. Each item has a clear key, and each generated instance can be referenced by that key.
This matters during reviews. A Terraform plan that says aws_iam_user.users["alice"] is being updated is much easier to understand than a plan that refers only to index 0.
for_each vs count
Terraform count and for_each both allow teams to create multiple instances from one block. The difference is how Terraform identifies each instance.
count uses numeric indexes starting from zero. Terraform’s documentation explains that count.index identifies each instance by its position.
for_each uses keys. Those keys come from a map or set.
Use count when you need a simple number of nearly identical resources.
Use for_each when each instance needs a meaningful identity or different values.
The biggest practical difference is stability. If you remove one item from a list used with count, Terraform may shift indexes and plan changes to resources that should not change. With for_each, each resource is tied to its key, so removing one item does not automatically shift the identity of the others.
HashiCorp’s guidance is straightforward: use count for nearly identical instances, and use for_each when instance arguments require distinct values that cannot be derived cleanly from an integer index.
Using for_each With Maps
Maps are one of the best inputs for Terraform for_each because each item already has a key and a value.
Here is a simple map example:
Terraform creates one user for each name.
Sets are unordered collections. Terraform does not treat them like lists with indexes. HashiCorp’s documentation notes that sets do not support direct index access because they are unordered.
That is one reason sets work well with for_each. The goal is not to rely on position. The goal is to create one instance per unique string.
Use sets when the item itself is enough to identify the resource.
Use maps when each item needs additional values.
The for_each List Workaround
A common Terraform error happens when teams try to use a list directly with for_each.
Terraform requires for_each to receive a map or a set of strings. It does not automatically convert lists or tuples to sets for for_each. HashiCorp’s documentation states that Terraform does not implicitly convert lists or tuples to sets for for_each, so teams must explicitly convert when needed.
This is a clean pattern because the resource key becomes the instance name.
Nested for_each
Nested for_each is useful when infrastructure has parent-child relationships.
For example, you may have multiple applications, and each application may need multiple subnets, roles, or rules. Terraform can handle this, but the data structure needs to be shaped carefully.
A common approach is to flatten nested data before using it with for_each.
Nested for_each can be powerful, but it should be used carefully. If the local transformation becomes difficult to understand, the module may need to be simplified.
The goal is not to make Terraform clever. The goal is to make infrastructure easier to manage.
Terraform for_each Module Example
Terraform for_each is not limited to resources. It can also be used with modules.
This is useful when you want to deploy the same module multiple times with different inputs.
Terraform creates one module instance for each environment.
Those module instances can be referenced by key:
module.network["dev"]
module.network["prod"]
This pattern is helpful for platform teams because it supports standardization without forcing every environment to be identical.
The module defines the approved infrastructure pattern. The input map defines how each environment should be configured.
Chaining for_each Between Resources
Terraform can also chain for_each between resources.
This means one resource using for_each can become the input for another resource. HashiCorp’s documentation describes this as a useful pattern when there is a one-to-one relationship between objects, such as creating one internet gateway for each VPC.
This tells Terraform that each VPC should have a matching internet gateway.
Chaining can make relationships easier to express because Terraform understands the connection between the resources.
It also keeps keys consistent across related resources.
Common for_each Errors and Fixes
One common error is using a list directly. The fix is to use toset() for a list of strings or convert a list of objects into a map.
Another common error is using values that are not known until apply. Terraform needs the for_each collection before it can perform remote resource operations. The keys must be known during planning. HashiCorp’s documentation explains that for_each values must be known before Terraform performs remote actions.
A third error is using sensitive values in for_each. Terraform does not allow sensitive values as for_each arguments because the keys are used to identify instances and may appear in UI output.
Another error is changing keys without understanding the impact. If a key changes, Terraform treats it as a different instance. That may cause Terraform to destroy the old instance and create a new one.
may cause Terraform to replace the resource because the key changed.
This is why stable keys matter. Choose keys that represent long-term identity, not temporary labels.
Best Practices for Terraform for_each
Use maps when resources need unique settings. Maps make it clear which values belong to which resource.
Use sets when each item is just a unique string. This is useful for users, simple names, or IDs.
Avoid using unstable keys. Keys should not depend on generated IDs, timestamps, random values, or values that may change often.
Prefer meaningful keys. A key like prod-api is easier to review than an index like 2.
Keep input variables strongly typed. Use map(object(...)) or set(string) instead of loose types where possible.
Be careful when renaming keys. A key change can affect resource identity.
Review Terraform plans closely. A small input change can create, update, or destroy multiple instances.
Use for_each for repeatable patterns, not for hiding complex logic. If a loop makes the configuration harder to understand, the design may need to be simplified.
The Governance Angle for env zero
Terraform for_each helps teams reduce duplication, but it also increases the importance of review and governance.
One change to a map can affect many resources. One renamed key can cause replacement. One new environment entry can trigger an entire module deployment. These changes may be valid, but teams need a clear way to review them before they are applied.
That is where env zero fits into the Terraform workflow.
env zero sits around Terraform and OpenTofu to help teams manage plan-and-apply workflows, approval gates, RBAC, policy controls, drift detection, cost visibility, and standardized execution across teams and environments.
For for_each workflows, this matters because reusable infrastructure patterns often scale quickly. A platform team may define one module, but many teams may use it across many environments. Governance ensures those changes are visible, reviewed, and controlled.
Terraform defines the infrastructure pattern. for_each helps repeat that pattern safely. env zero helps teams govern how those repeated changes move through the organization.
Summary
Terraform for_each is one of the most useful features for teams that want to reduce duplication in infrastructure as code.
It allows teams to create multiple resources or module instances from maps and sets, while giving each instance a stable and meaningful identity. Compared with count, for_each is often safer and easier to review when each instance has different values.
The best use cases include repeated cloud resources, environment-based modules, IAM users, security rules, networking components, and standardized platform modules.
The key is to use for_each carefully. Choose stable keys, use clear input types, avoid unnecessary complexity, and always review the plan before applying changes.
For infrastructure teams scaling Terraform in 2026, for_each is more than a loop. It is a practical way to make Terraform configurations cleaner, more reusable, and easier to govern across teams.
.webp)