Terraform for_each – A Simple Tutorial with Examples

Terraform

Terraform, an open-source Infrastructure as Code (IaC) tool developed by HashiCorp, enables developers and system administrators to automate the provisioning, updating, and versioning of infrastructure. It allows users to define infrastructure using a declarative configuration language and manage the lifecycle of cloud services such as virtual machines, networking, storage, and more across a variety of providers like AWS, Azure, and Google Cloud.

Among Terraform’s powerful features is its ability to create multiple instances of similar infrastructure resources efficiently. To accomplish this, it offers constructs such as count and for_each. While both serve a similar purpose, they differ significantly in behavior and use cases.

This article dives deep into the conceptual understanding of the for_each meta-argument, particularly focusing on what it is, how it differs from count, and when and why to use it.

Meta-arguments in Terraform: A Conceptual Overview

Before exploring for_each, it’s important to understand what meta-arguments are in Terraform. Meta-arguments are special configuration elements used to modify or influence how a particular block in a configuration behaves. These blocks may define a resource, module, or data source.

Some commonly used meta-arguments include:

  • count: Used to define how many instances of a resource to create.
  • for_each: Provides more control by allowing you to iterate over sets and maps.
  • depends_on: Specifies explicit dependencies between resources.
  • provider: Specifies which provider configuration to use for a resource.

These arguments are not specific to any one cloud provider or resource type—they influence the structure and logic of Terraform configurations themselves.

Why Use Meta-arguments Like for_each?

Infrastructure rarely comes in single units. You often need multiple servers, databases, subnets, or storage buckets. Writing out separate configurations for each is repetitive and error-prone. Meta-arguments solve this problem by allowing you to define a single configuration that scales automatically.

Using constructs like for_each helps simplify configurations, reduce duplication, and improve maintainability. This is especially important in modern DevOps and cloud engineering practices where scalability, repeatability, and version control are essential.

The Problem with the count Meta-argument

At first glance, count seems like a straightforward way to create multiple resources. You just define how many instances you want, and Terraform creates that number. However, there are key limitations:

  1. Index-based identification: Resources are indexed numerically, meaning their identities are tied to their position in a list. If the list changes (for instance, by removing an element in the middle), the indices shift, causing Terraform to interpret this as a change in existing resources. This may result in destroying and recreating resources unnecessarily.
  2. Poor resilience to change: Because of its reliance on order, using count often leads to unintended behavior during updates. For example, if you delete the second element in a list of five, Terraform may destroy the second instance and recreate three others with new identifiers.
  3. Not ideal for maps or sets: count is designed for lists. If your data structure is a map or a set, you must convert it to a list, which may cause additional complications or semantic loss.

For small, static datasets, count might be sufficient. However, as infrastructure grows in size and complexity, these limitations become more significant.

Introducing the for_each Meta-argument

The for_each meta-argument addresses the limitations of count by allowing you to iterate over more structured and meaningful data collections like sets and maps. This provides clearer resource identification and better support for dynamic environments.

Instead of relying on index numbers, each instance created by for_each is associated with a unique key. This key comes from the elements of the set or the keys of the map being iterated over. As a result, when an element is removed or added, Terraform can identify what exactly changed, and only apply changes to the affected instances.

Key Features of for_each:

  • Works with sets and maps: This makes it versatile for different data structures.
  • Identifies resources by key: Avoids the brittleness of index-based references.
  • Improves readability and manageability: Especially helpful in large-scale deployments.
  • Works across block types: It can be applied to resource, data, and module blocks.

When Should You Use for_each?

Here are some ideal scenarios where for_each is more appropriate than count:

1. Managing Resources with Unique Identifiers

When your infrastructure elements have a unique name or label (e.g., hostnames, region tags, or service types), for_each helps clearly associate configuration with those identifiers.

2. Working with Dynamic or Unordered Sets

If your data structure is unordered (like a set) or when the order isn’t significant, using for_each avoids the pitfalls of positional references.

3. Avoiding Unintended Destroy-and-Recreate Operations

Because for_each tracks changes using keys, it only affects items that have truly changed. This minimizes downtime and reduces risk in production systems.

4. Chaining Resources Together

When you need to create one-to-one mappings between resources, such as attaching one EBS volume per EC2 instance, for_each allows chaining these dependencies cleanly using shared keys.

How for_each Works (Conceptually)

At a high level, here’s how Terraform processes a for_each block:

  1. Evaluate the collection: Terraform reads the set or map provided to for_each.
  2. Create an instance per key: For each item in the collection, an instance of the resource (or module or data source) is created.
  3. Bind contextual variables: During creation, Terraform makes each key and its corresponding value available as contextual variables (commonly called each.key and each.value in code).
  4. Track resources by key: In the state file and execution plan, Terraform associates each instance with the key, not a positional index.

This process enables more intelligent and targeted changes when configuration or inputs evolve over time.

Differences Between count and for_each

Let’s summarize the main differences in behavior and usage between count and for_each.

Aspectcountfor_each
Works withLists onlySets and maps
IdentificationPositional (indexed)Key-based (mapped)
StabilityFragile when list changesMore stable with key tracking
Best forSimple repetitionDynamic, labeled repetition
Resource referenceresource[index]resource[key]
UsabilityEasier to write, harder to maintainSlightly complex, but scalable

While count is simpler and can be easier for beginners, for_each is generally the better choice in professional environments where clarity and maintainability are priorities.

Practical Analogy

To better understand how for_each works, consider the following analogy:

Imagine you are managing a fleet of rental cars. Each car has a unique license plate number. If you use a system based on position (like count), your system might refer to them as Car1, Car2, Car3. But if you remove Car1 from your system, all the others shift their positions. That creates confusion and leads to errors.

Instead, if you manage cars by their license plate numbers (like for_each), removing Car1 doesn’t affect Car2 or Car3. You’re working with identifiers, not positions. That’s exactly the improvement for_each brings to Terraform configuration.

Advantages of Using for_each

  • Improved clarity: You can tell exactly which resource is being referenced just by the key.
  • Resilience to change: Removing or adding elements doesn’t disrupt unrelated resources.
  • More expressive configurations: Allows for cleaner mapping between configuration and real-world infrastructure objects.
  • Chaining support: Enables seamless creation of dependent resources using shared keys.
  • Scalability: Ideal for managing large-scale infrastructure environments.

Common Misconceptions

There are a few misunderstandings around for_each that are worth addressing:

“for_each is just a fancier count”

While both serve similar functions, for_each is fundamentally different in how it handles data and manages state. The distinction between indexed versus keyed references makes a big difference in long-term maintainability.

“It’s only useful for complex scenarios”

Even simple infrastructure setups can benefit from for_each. For example, if you need to create a few instances with meaningful names, for_each makes this task easier and safer.

“Using for_each is always better than count”

Not necessarily. count can still be useful when working with simple numeric repetitions or when backward compatibility is a concern. It’s important to choose the right tool for the task.

Summary of Key Concepts

  • Meta-arguments modify the behavior of Terraform blocks.
  • for_each is ideal for iterating over sets and maps, offering stable and meaningful resource references.
  • Unlike count, for_each uses keys rather than index positions to identify resources.
  • for_each can be used in resources, modules, and data blocks, allowing for powerful and reusable configurations.
  • It provides better change management, avoids unintended resource destruction, and enhances readability.

Now that you’ve grasped the theoretical foundation of how for_each works in Terraform, the next step is to explore how it can be applied in real-world configurations. In Part 2 of this series, we will examine specific use cases where for_each is employed across resource, module, and data blocks to solve common infrastructure problems.

Practical Use Cases and Applications of for_each in Terraform (No Coding)

In this series, we introduced the for_each meta-argument in Terraform, discussed how it differs from count, and covered why it’s beneficial for managing scalable and dynamic infrastructure. Now we will explore real-world use cases, common applications, and best practices for implementing for_each across different resource types—without showing code.

Our goal is to help you build a strong mental model of how and when to use for_each in practical settings, especially in real-world DevOps workflows involving cloud resources such as AWS, Azure, and GCP.

The Nature of Infrastructure: Why Repetition Matters

Infrastructure provisioning often involves creating similar resources repeatedly:

  • Multiple virtual machines
  • Several subnets across different regions
  • Distinct IAM policies per team
  • Many storage buckets with unique names
  • Replicated environments (staging, production, testing)

Manually writing a configuration block for each instance is time-consuming and error-prone. Terraform’s for_each allows you to define these repeated elements once, and iterate over dynamic collections to produce multiple uniquely identified resources.

Let’s now dive into the practical use cases.

Use Case 1: Creating Multiple Virtual Machines with Unique Names

Imagine you need to provision several virtual machines, each with its own hostname or label such as web1, web2, and web3. Instead of repeating the entire configuration three times, you can prepare a list of these labels and loop through them using for_each.

Here’s how it works conceptually:

  • Define a collection (e.g., set or map) with unique hostnames.
  • Loop through the collection using for_each.
  • For each item, Terraform creates a resource and tags it with the appropriate hostname.

This ensures that:

  • The resources are created with unique and meaningful identifiers.
  • You can remove or add machines without disrupting the others.
  • You have better traceability in logs, tags, and dashboards.

This is extremely helpful in managing autoscaling groups, web servers, and container nodes.

Use Case 2: Deploying Subnets Across Multiple Availability Zones

Let’s say you need to deploy subnets in different availability zones. You have:

  • Three availability zones: zone-a, zone-b, zone-c
  • One subnet in each zone, with unique CIDR blocks

With for_each, you can:

  • Create a map of zone names to CIDR blocks
  • Use the zone names as keys
  • Terraform will then create a subnet per zone with its assigned IP range

The major benefits include:

  • Consistent resource naming aligned with zone identifiers
  • Minimal effort to expand infrastructure to new zones
  • Targeted updates, since Terraform can identify which subnet changed based on zone key

This approach is particularly beneficial in multi-region cloud architectures.

Use Case 3: Assigning IAM Roles or Policies Per Team

Managing access control is another scenario where for_each shines. Suppose your organization has the following teams:

  • Dev
  • QA
  • Ops

Each team requires a specific IAM role or set of permissions. With for_each, you can:

  • Define a map where keys are team names and values are the role definitions
  • Loop through the map
  • Create one IAM role per team, tied to the key

This offers the following benefits:

  • Clear role mapping between teams and permissions
  • Easier maintenance of role definitions
  • Smooth onboarding when a new team joins—just add a new key-value pair

Using this pattern supports scalable and secure identity management.

Use Case 4: Managing Resource Tags with Key-Value Pairs

Tagging is critical for cost tracking, environment separation, and automation. Using for_each, you can loop through a collection of key-value pairs and apply them as tags to resources such as instances, volumes, or buckets.

The advantages:

  • Centralized tag definitions
  • Reusable tagging strategy across multiple resources
  • Consistency in how tags are applied, reducing human error

For example, tags like Environment = Production, Owner = DevOps, and Team = Backend can be managed dynamically using for_each on a map.

This makes infrastructure self-documenting and easier to audit.

Use Case 5: Provisioning Cloud Storage Buckets Per Environment

You may need to create cloud storage buckets for different environments like:

  • Development
  • Testing
  • Production

Each environment may require different lifecycle policies or naming conventions.

With for_each, you can:

  • Define a map where each environment is a key
  • Provide specific configurations as values
  • Terraform will loop through and provision a storage bucket for each

This supports:

  • Environment-specific customization
  • Automated deployment pipelines that reuse the same logic
  • Easier environment separation and resource isolation

Ideal for managing CI/CD pipelines, logging buckets, or backups.

Use Case 6: Managing DNS Records for Multiple Domains

When managing infrastructure for several domains, creating DNS records manually is inefficient.

With for_each:

  • Use a map where each key is a domain name
  • Values can represent IP addresses or TTL settings
  • Loop through the map to provision DNS records

Benefits include:

  • Improved clarity of which record belongs to which domain
  • Fast onboarding of new domains
  • Lower risk of misconfigured DNS entries

This is commonly used in multi-tenant SaaS architectures.

Use Case 7: Instantiating Modules with Different Inputs

Terraform modules help you encapsulate reusable infrastructure logic. for_each allows you to instantiate the same module multiple times, each with different parameters.

Example scenarios:

  • Deploying multiple Kubernetes clusters with varying settings
  • Launching different environments using a shared VPC module
  • Creating service replicas across cloud accounts

You can:

  • Create a map of configurations for each environment or service
  • Use for_each to loop over the map and pass distinct inputs to the module
  • Deploy unique yet standardized infrastructure pieces

This enables DRY (Don’t Repeat Yourself) practices and reusability at scale.

Common Patterns and Best Practices

Now that you’ve seen the wide applicability of for_each, let’s explore some of the common patterns and best practices for using it effectively.

Pattern 1: Use Maps for Customization

Maps are often better than sets when working with for_each because they allow you to attach metadata to each item.

For example:

  • Key: resource name
  • Value: configuration details like region, size, tags

This provides greater control and flexibility in defining behavior per resource.

Pattern 2: Avoid Implicit Conversions

Always make sure that the data structure passed to for_each is explicitly a map or set. Relying on implicit conversions (e.g., turning a list into a map) can lead to unexpected behavior and reduce code clarity.

Pattern 3: Reference Keys, Not Indices

Unlike count, where you refer to resources with an index, for_each uses keys. This makes your configurations more readable and less fragile when elements are added or removed.

Pattern 4: Externalize Data with Locals or Variables

Use locals or input variables to define the collections for for_each. This:

  • Improves reusability
  • Makes the main configuration cleaner
  • Allows for easier overrides in different environments

Pattern 5: Plan for Future Changes

Because for_each keys are used as resource identifiers, changing keys in the future will result in Terraform destroying the old resource and creating a new one. So:

  • Choose keys that are stable and meaningful
  • Avoid keys that change often (like timestamps)

Pattern 6: Document Your Collections

If you use complex maps or sets with nested data, document them using comments or markdown in your Terraform repo. This helps collaborators understand what each part of the structure does.

When to Avoid Using for_each

While powerful, for_each is not always the right choice. Avoid it in these cases:

  • When you need simple numeric repetition (use count)
  • When the data lacks meaningful keys (creating a map might be forced or unnecessary)
  • When your team is unfamiliar with its behavior (risk of misuse or confusion)

The rule of thumb is: Use for_each when structure and identity matter.

Summary of What You Learned

In this article, we examined how for_each can be applied in various real-world scenarios. You now understand:

  • How for_each helps you scale and manage resources with unique identifiers
  • When to use it over count, especially for maps and sets
  • Why it’s valuable in tagging, IAM, DNS, environments, subnets, and modules
  • Best practices for writing clean, resilient for_each logic

Understanding the conceptual power of for_each gives you the ability to design modular, scalable, and DRY infrastructure with ease—even before diving into syntax.

Troubleshooting, Pitfalls, and Advanced Tips (No Coding)

we laid the foundation by explaining the purpose and behavior of for_each. we explored how it is used in real-world scenarios like creating subnets, IAM roles, or modules dynamically.

  • Common mistakes and how to avoid them
  • Troubleshooting techniques
  • Behavior of for_each during updates and deletions
  • Advanced patterns and strategic advice for real-world Terraform projects

By the end of this article, you’ll know how to handle for_each like a pro, with confidence and clarity—even in large-scale production environments.

Why Is Troubleshooting for_each Challenging?

for_each feels intuitive until something breaks or behaves unexpectedly. Because for_each is tied to keys in a map or set, the way Terraform tracks and applies changes becomes key-sensitive.

Common frustrations arise from:

  • Accidentally changing the structure of the keys
  • Unintentional resource replacement
  • Conflicts between count and for_each
  • Unexpected behaviors during plan or apply phases

Understanding how Terraform handles resource identity and state is crucial.

Common Errors and How to Fix Them

1. Changing the for_each Key Leads to Resource Recreation

The problem:
You rename or modify a key in your for_each map.

What happens:
Terraform destroys the old resource and creates a new one—even if only the key name changed, not the underlying values.

Why:
Terraform uses the key as a unique identifier in the state file.

Solution:

  • Choose stable and meaningful keys (e.g., region names, environment names).
  • Avoid keys that might change, such as user input or dynamic strings.
  • If you must rename a key, plan for the destruction and recreate process explicitly.

2. Type Mismatch Between for_each and the Collection

The problem:
You try to loop over a list, but for_each expects a map or set.

What happens:
Terraform throws a type error or creates unexpected resource names (like “0”, “1”, “2”).

Why:
Unlike count, which uses numeric indexing, for_each requires a collection with distinct keys.

Solution:

  • Convert lists to sets only when keys aren’t required.
  • Prefer maps when you want both unique identifiers and values.
  • Avoid using for_each with duplicate values.

3. Mixing count and for_each on the Same Resource Type

The problem:
One resource block uses count, another uses for_each, and both manage the same resource type.

What happens:
Terraform may get confused during updates or generate conflicting plans.

Why:
count and for_each handle internal indexing differently and should not be mixed for the same purpose.

Solution:
Pick one looping method per resource type in a module. Use count for simple repetition and for_each for resource-specific customization.

4. Removing a Key from the Map Deletes a Resource

The problem:
You remove a key from the for_each map.

What happens:
Terraform sees that the resource tied to the missing key no longer exists and schedules it for deletion.

Why:
for_each directly ties the existence of resources to the presence of their keys in the collection.

Solution:
Use caution when pruning keys. If you need to decommission resources, do so intentionally and with a clean workflow:

  • Comment out the entry explicitly.
  • Allow one plan cycle to remove it.
  • Reorganize collections afterward.

5. Resource Address Changes After Modifying Keys

The problem:
You tweak the key, and now the resource’s address in state changes.

What happens:
Terraform treats this as a new resource and plans a destroy + create sequence.

Why:
In Terraform’s state, a key determines the identity of the resource. Any change breaks the identity link.

Solution:
Avoid cosmetic changes to keys unless necessary. If required, use terraform state mv to rename resources safely without recreation.

Debugging Tips for for_each

When for_each doesn’t behave as expected, use the following strategies:

1. Use Terraform Plan Verbosely

  • Run terraform plan regularly to preview changes.
  • Use terraform plan -out=planfile to save the plan.
  • Review diffs carefully to understand what’s being created, modified, or destroyed.

This helps catch unintended consequences of key changes.

2. Look at Terraform State

  • Use terraform state list to inspect current resources.
  • Use terraform state show on a specific resource to see its details and key mapping.

This reveals whether the keys are being interpreted the way you expect.

3. Log Variables with Null Resources

If you’re unsure how your map or set is being evaluated, output it temporarily using null resources, locals, or output variables.

While this doesn’t modify infrastructure, it provides visibility.

4. Use Resource Targeting with Caution

If a single resource in a for_each loop is problematic, you can apply it selectively using:

bash

CopyEdit

terraform apply -target=resource_type.resource_name[“key”]

But don’t overuse this. It can cause state inconsistencies if not handled carefully.

Advanced Tips for Managing for_each at Scale

Tip 1: Isolate Reusable Data into Locals

Use locals blocks to store collections for for_each. This keeps your main resource configuration clean and helps in reusing the same map or set across multiple resources.

Tip 2: Parameterize Modules with for_each

You can pass a map of configurations to a module and use for_each to deploy multiple environments or clusters with different specs. This supports multi-region, multi-tenant, or multi-environment architectures efficiently.

Tip 3: Use Descriptive Keys

Pick keys that reflect real-world identifiers—like:

  • Environment names (dev, qa, prod)
  • Team names (backend, frontend)
  • Region codes (us-east-1, eu-west-2)

Avoid using generic numeric keys like “0” or “1”.

Tip 4: Combine with Dynamic Blocks

Though not shown in this no-code article, combining for_each with dynamic blocks allows for fully dynamic nested structures, such as dynamic rules inside a firewall or ACL configuration. Conceptually, this means one layer of repetition can spawn multiple sublayers.

Tip 5: Use Terraform Console for Exploration

terraform console is an interactive shell that allows you to:

  • Evaluate expressions
  • Test your maps and sets
  • See how for_each would interpret a given value

Great for experimentation before writing any configuration.

Summary: What You’ve Mastered

In this third and final part of the series, you’ve learned to:

  • Identify and fix common for_each issues
  • Troubleshoot key errors, type mismatches, and unintended deletions
  • Interpret Terraform state and plan output
  • Use advanced strategies to scale infrastructure dynamically and predictably

By treating for_each as a tool for clarity, maintainability, and scalability, you gain full control over how your infrastructure is described, managed, and deployed—without needing to write repetitive configuration.

Final Thoughts

Terraform’s for_each is not just a looping mechanism—it’s a strategic design pattern that lets you translate real-world relationships into infrastructure logic. When used thoughtfully, it leads to clean, predictable, and scalable automation.

You are now equipped to:

  • Use for_each conceptually for planning
  • Avoid the most common traps
  • Create dynamic architectures with ease

Whether you’re managing a few virtual machines or a fleet of multi-region services, for_each will become an essential tool in your DevOps skillset.