As your Terraform configurations grow, managing resources dynamically and minimizing repetitive code becomes essential. The good news is that Terraform offers powerful tools to help with this.
The for_each
meta-argument simplifies resource creation by allowing you to dynamically handle unique identifiers or configurations for each resource. This flexibility results in cleaner and more organized infrastructure-as-code.
Never heard of it? No problem!
In this guide, I’ll walk you through what for_each
does, how to use it effectively, and the best practices to keep in mind for smooth deployments.
Whether you’re working on simple setups or scaling across multiple clouds, mastering for_each
will make your Terraform configurations more manageable and efficient.
Sidenote: Want to dive deeper into learning and using Terraform? Then check out my Terraform Bootcamp course.
I cover everything from the fundamentals all the way to provisioning real-world cloud infrastructure on AWS. You can go from total beginner to being able to get hired as a DevOps Engineer or System Administrator, so it’s a great resource for any skill level.
With that out of the way, let’s get into this guide…
When setting up infrastructure in Terraform, you’ll often need several instances of the same resource—like virtual machines, databases, or network settings.
To help you automate these repetitive tasks, Terraform offers two meta-arguments:
count
andfor_each
Knowing when to use each one will save you effort and keep your code clean and easy to manage, so let's break these down.
The count
meta-argument is the simplest way to create multiple identical resources. It’s great for when you know exactly how many copies you need, and when each one can have the same configuration without needing any customizations.
For example
Let’s say you’re setting up a web app in AWS, and you want three virtual machines (VMs) to sit behind a load balancer, so that your app can handle high traffic and prevent downtime if a server goes down.
Well because each VM will be identical, count
makes your job easy:
resource "aws_instance" "web" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
With this configuration, Terraform will create three identical instances of the VM, each with the same Amazon Machine Image (AMI) and instance type. Super simple.
count
falls shortHowever, while count
is powerful, it has limits. Specifically, it struggles when you need each instance to have unique properties—such as different tags, individual names, or specific configurations.
In these scenarios, the for_each
meta-argument becomes invaluable.
The for_each
meta-argument allows you to create multiple resources with distinct configurations. Think of it as a loop that iterates over a collection, enabling you to define unique attributes for each resource.
Here’s the general syntax for for_each
:
resource "resource_type" "resource_name" {
for_each = <ITERABLE>
# Additional properties
}
Let’s break this down:
for_each
: This defines the collection over which to loop. It can be a set, list, or mapeach.key
and each.value
: These placeholders represent the key and value of each item as Terraform iterates through the collection. When for_each
is set to a list or set, each.key
and each.value
will be the same. For a map, each.key
represents the map key, and each.value
represents the map valueUnlike count
, for_each
supports sets, lists, and maps, enabling you to dynamically define unique values for each instance. This flexibility is essential when managing resources that need to be distinct from one another, even if they share a basic configuration.
TL;DR
Use for_each
instead of count
when:
Lists, sets, and maps are fundamental data structures used in Terraform to organize and manage collections of items. Choosing the right data structure is crucial for effective resource management.
So let's break them down.
Lists are ordered collections that can contain duplicate values. They are best used when the order of elements matters or when repetitions are acceptable.
For example, if you’re managing deployment steps, a list could look like this: ["install", "configure", "deploy"]
. A key feature of lists is that you can reference items by their index, which is useful in scenarios where sequence matters.
Benefits of using Lists:
Sets are unordered collections that automatically eliminate duplicate items. They are ideal when you need to ensure that all items are unique, simplifying the management of distinct items.
For instance, a set might define unique project names like toset(["projectA", "projectB", "projectC"])
. The primary advantage of sets is their ability to prevent duplicates, making them a great choice for unique identifiers.
Benefits of using Sets:
Maps are collections of key-value pairs that allow you to create associations between identifiers and their corresponding values.
They are particularly useful when resources require unique properties, such as IP addresses for virtual machines. Maps enable clear relationships between keys and their associated values, making configurations easier to manage and understand.
Benefits of using Maps:
Understanding these data structures and their appropriate use cases will help you make informed decisions about which to use with for_each
, ensuring that your Terraform configurations are both effective and efficient.
So let's walk through how to use each of them.
When working with cloud infrastructure, there are often scenarios where you need to set up multiple resources that share a common theme. This is where using lists with the for_each
meta-argument in Terraform becomes incredibly helpful.
They allow you to maintain a specific order and can include duplicate values, making them ideal for situations where the sequence matters or when you need to handle a set of similar resources.
For example
Imagine you’re managing a cloud project that requires multiple storage buckets for different environments: development, staging, and production. Instead of writing repetitive code for each bucket, you can leverage a list to simplify the process.
First, define a variable to represent the environments:
variable "environments" {
type = list(string)
default = ["development", "staging", "production"]
}
Now, you can use this list in your aws_s3_bucket
resource block to dynamically create each bucket:
resource "aws_s3_bucket" "environment_buckets" {
for_each = toset(var.environments)
bucket = "${each.value}-storage"
tags = {
Name = each.value
}
}
So what's happening here?
Well in this configuration, the line for_each = toset(var.environments)
converts your list of environments into a set, ensuring that each name is unique.
And because for_each
iterates through each item in the set, the expression ${each.value}-storage
generates a unique bucket name by appending “-storage” to each environment name.
What this means is that in the end, you'll have buckets named development-storage
, staging-storage
, and production-storage
.
This approach simplifies management and avoids confusion about which bucket corresponds to which environment. Even better, if you add another environment later, you can just update the list and Terraform will create the new bucket automatically.
Now that we've explored using lists with for_each
, let's see how sets can further enhance your resource management by ensuring uniqueness with Sets.
When you need to manage multiple resources in Terraform, using sets with the for_each
meta-argument is a fantastic approach.
Why? Well, sets are particularly useful when you want to ensure that all items are unique and don’t require a specific order. This means you can easily manage collections of distinct items without the risk of duplicates, which is crucial for maintaining clarity and efficiency in your configurations.
For example
Let’s say you are working on a cloud project that involves creating separate storage buckets for different teams within your organization.
Each team needs its own bucket, and you want to ensure that no team name is repeated. By using a set, you can dynamically create buckets while maintaining uniqueness.
Here’s how you can set this up:
resource "aws_s3_bucket" "project_buckets" {
for_each = toset(["projectA", "projectB", "projectC"])
bucket = "${each.key}-storage"
tags = {
Name = each.key
}
}
So what's happening here?
In this example, toset(["teamA", "teamB", "teamC"])
converts the list of team names into a set. Then, the for_each
statement will iterate through each item in this set, allowing you to create a unique bucket for each team effortlessly.
Not only that, but the expression ${each.key}-storage
appends “-storage” to each team name, ensuring you have unique bucket names like teamA-storage
, teamB-storage
, and teamC-storage
, so that each bucket also receives a Name
tag based on each.key
, making it easy to identify which bucket belongs to which team.
Using maps with for_each
is particularly advantageous when your resources require unique attributes that need clear associations. Maps also help facilitate efficient management of configurations by allowing you to create key-value pairs.
For example
When creating virtual machines in your cloud environment, each VM might need a specific private IP address. By using a map, you can associate each instance name with its corresponding IP address seamlessly.
Here’s how to set this up:
resource "aws_instance" "web_servers" {
for_each = {
"web1" = "10.0.1.10"
"web2" = "10.0.1.11"
}
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
private_ip = each.value
tags = {
Name = each.key
}
}
So what's happening here?
In this example, the for_each
expression is defined as a map where each key (like web1
and web2
) corresponds to an instance name, while the value is the unique IP address assigned to that instance.
And as Terraform processes this block, private_ip = each.value
assigns the respective IP address from the map, ensuring clear identification. For instance, web1
gets 10.0.1.10
, and web2
receives 10.0.1.11
.
Additionally, each instance is tagged with its unique name from each.key
, simplifying identification of which VM corresponds to which function in your infrastructure.
As you dive into using for_each
, here are some handy tips to navigate common pitfalls and ensure a smoother experience.
Ensure that your for_each
collections are defined correctly. Use sets or lists when working with identical items, and opt for maps when each instance needs specific attributes.
Misunderstandings here can lead to unexpected errors that disrupt your deployment.
Steer clear of hardcoding values like IP addresses or secrets directly in your for_each
collections. This practice can cause issues during updates.
Instead, consider using variables or data sources, which make your configurations more adaptable and secure.
Establish descriptive keys (e.g., frontend, backend) for your resources and add comments where necessary.
This approach enhances the readability of your configurations, making it easier for you and others to understand the purpose of each component. Not only that, but clear naming conventions can save time during troubleshooting.
Be mindful when changing keys in a map used by for_each
. Terraform will treat this as a brand-new resource, leading to the deletion of the existing resource and the creation of a new instance.
This can disrupt your services, so always review planned changes with Terraform plan before applying them.
If you encounter unexpected errors, double-check the data structures you are using, so you can ensure that you are not mixing up lists, sets, and maps, as this can lead to complications. Always test configurations in a development environment before applying them to production.
By keeping these tips in mind and being aware of common issues, you can reduce errors and enhance the maintainability of your Terraform configurations.
So as you can see, the for_each
meta-argument in Terraform is a game changer for managing your infrastructure as code. It gives you the flexibility to create multiple resources dynamically, all while keeping your configurations clean and easy to manage.
Now that you understand how powerful for_each
can be, I highly recommend you test it out and experiment with it in your own Terraform projects.
Try applying these concepts in different scenarios and see how they can streamline your workflow. The more you practice, the more confident you’ll become.
Happy Terraforming! Your journey to mastering infrastructure as code starts now.
Don’t forget! If you want to dive deeper into learning and using Terraform, check out my Terraform DevOps Bootcamp.
I cover everything from the fundamentals all the way to provisioning real-world cloud infrastructure on AWS and getting hired!
Once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students and working professionals.
Not only that, but if you decide to join as a ZTM member - you’ll also get access to every course in the Zero To Mastery library!