Terraform for loop helps you write simplified, repeatable code used to deploy resources. In this article, we’ll explore for loops using for_each
and count
, how and when to use them, example scenarios, best practices, and much more.
Disclaimer
All use cases of Terraform for loops discussed here work similarly in OpenTofu, the open-source Terraform alternative. However, to keep it simple and familiar for DevOps engineers, we will refer to these as “Terraform” for loops throughout this blog post.
What are a Terraform for Loops
Using loops in Terraform serves as an easy way to create multiple resources without repeating the same code, which is in line with the DRY (Don't Repeat Yourself) code principle.
Utilizing loops relies on the use of for_each
and count
meta-arguments. When used together with the for expression, it helps you transform values on the go. You can use conditionals in loops to include or exclude an item while creating some dynamic or complex configuration.
Once the looping operation is performed, it returns a new list or map that you can use to configure your resources, outputs, and variables. Typically, loops are helpful in creating new data structures and filtering the existing map or list, producing reusable and less error-prone configurations.
When Should You Use Loops?
Terraform loops make it easier to create multiple resources efficiently with reusable code that is easy to maintain.
Let’s look at some of the use cases of using Terraform for loop:
Reduce code repetition - When deploying multiple resources, like IAM roles or security groups, writing each one individually can be time-consuming and error-prone. Using for loops in Terraform allows you to create all necessary resources within a single code block, ensuring consistency and making your infrastructure code more manageable.
Conditional resource deployments - When you want to deploy multiple resources based on conditions, let's say two S3 buckets for the ‘dev’ environment and four for ‘prod’, you can create them using the loops in combination with conditional logic, again saving time and ensuring consistency and code DRYness.
These are just two common use cases. Generally speaking, Terraform for loops is just a useful way to create an efficient and DRY code, and could be helpful in addressing various use cases and requirements, some of which we will dive into below.
For_each Loop
Using for_each
loops enables you to create multiple resources or modules by iterating over a set of input values, such as a list or map. Instead of manually defining each resource, for_each
generates an instance for every item, following the DRY principle.
When you use for_each
in a resource block, Terraform provides each object with each.key
and each.value
. These allow you to apply specific configurations to each instance, ensuring that your infrastructure is consistent and efficiently managed without repetitive code.
Syntax of for_each loop
The syntax for the for_each
loop in Terraform is:
for_each = collection
Breaking this down, here’s what each argument means:
- for_each: The meta-argument used to iterate over each item in the collection.
- collection: The map or set of values for which resources are created.
To show how this works, here is a code that creates multiple EC2 instances with unique configurations:
resource "aws_instance" "env0" {
for_each = var.instance_map
ami = each.value.ami
instance_type = each.value.instance_type
}
Note how the for_each
loop iterates over the instance_map
and dynamically sets the ami
and instance_type
for each instance in the map.
For Expression
The for
expression in Terraform helps you transform a value into a new list or map. Using for
expression, you loop through each item in the collection one by one.
While you are going through each item, it can be transformed or modified to your desired output value set. Now, the transformed value can be used in your configuration.
You can also use the if
clause with the for
expression to evaluate the input value on the basis of a condition. Using the conditional clause, you can filter the items in your map or lists while you iterate through them.
Syntax of for expression
The syntax for the for
expression in Terraform is:
[for item in collection : expression]
Here is what each of these arguments means:
- for item in collection: Specifies that Terraform should loop over each element in the collection (which can be a list or map).
- expression: Shows how each item is transformed or utilized in the new list or map.
To show how this works, let’s look at an example where for
is used to iterate over original_list
that contains environment names:
locals {
original_list = [‘dev’, ‘staging’, ‘prod’]
transformed_list = [for env in local.original_list : "${env}-environment"]
}
Note how the list is transformed by appending -environment
in each item, creating a new list transformed_list
with the modified values.
Count Meta-argument
The count
is used to create an exact number of instances of a resource with the same configuration.
Unlike for_each
, which is ideal for resources with distinct configurations, count
is simpler and more effective when you need to deploy multiple resources.
Syntax of count meta-argument
To understand how the count
meta-argument is used, let’s take a look at its syntax:
count = number_of_instances
Again, breaking things down:
- count: Takes an integer that specifies how many resources will be created.
- number_of_instances: The value passed to the
count
meta-argument.
To demonstrate how count
works, let’s create three EC2 instances with the same configuration by setting the count
value to three:
resource "aws_instance" "web_server" {
count = 3
ami = var.ami
instance_type = var.instance_type
}
This serves as an easy example of how count
for loops could save time, streamlining repetitive tasks.
For Loop Example Scenarios
Now, let’s look at some examples of deploying AWS resources using different types of loops in Terraform.
Using Terraform for_each to Deploy EC2 Instances
Let’s assume you need to deploy multiple EC2 instances with different configs using your Terraform code. You can use the for_each
loop to create them.
First, define an ‘instances’ variable in your var.tf file. This variable is a map where each key represents a unique EC2 instance configuration:
variable "instances" {
type = map(object({
ami = string
instance_type = string
}))
default = {
instance1 = { ami = "ami-12345678", instance_type = "t2.micro" }
instance2 = { ami = "ami-87654321", instance_type = "t2.small" }
}
}
Next, use the for_each
loop to iterate over instances and create two aws_instance
resources. The configuration value for ami
and instance_type
is passed using the each
object:
resource "aws_instance" "env0" {
for_each = var.instances
ami = each.value.ami
instance_type = each.value.instance_type
}
To see that it works, run the terraform init
and apply commands to deploy the two aws_instance
resources.
To verify the creation of these aws_instance
resources, go to AWS Management Console and navigate to the ‘EC2’ dashboard to see the newly created instances.
Using Terraform for_each Loop to Deploy S3 Buckets
In this example, we’ll deploy 3 S3 buckets using Terraform. First, define a ‘bucket_count’ variable in the var.tf file, which specifies the exact number of S3 buckets to be created:
variable "bucket_count" {
type = number
default = 3
}
Next, use a for
loop to dynamically generate a list of bucket_names
, such as ‘infrasity-bucket-1’, ‘infrasity-bucket-2’, and ‘infrasity-bucket-3’ based on the value of ‘bucket_count’ in main.tf file. This list provides unique names for each S3 bucket, ensuring they are easily identifiable:
locals {
bucket_names = [for i in range(var.bucket_count) : "infrasity-bucket-${i + 1}"]
}
Then, create the resource block to deploy S3 buckets with unique names using for_each
loop, iterating over the list of ‘bucket_names’ in main.tf file:
resource "aws_s3_bucket" "infrasity" {
for_each = toset(local.bucket_names)
bucket = each.key
}
To see that it works, apply the configuration to deploy the 3 S3 buckets.
You can then view your deployed S3 buckets by logging into the AWS Management Console and navigating to the ‘S3’ dashboard.
Deploy EC2 Instance using Terraform Count
In the above example, we learned to use for_each
loop. But what about when you are required to deploy multiple resources with the same configuration, such as EC2 instances?
In this scenario, using for_each
loop only adds to the complexity of the code. Since this is a simple use case that does not require allocating different configurations for each instance, using Terraform count
is ideal and efficient.
Write the resource block to deploy EC2 instances with count
value set to 3, which instructs Terraform to create three EC2 instances:
resource "aws_instance" "Infrasity" {
count = 3
ami = "ami-12345678"
instance_type = "t2.micro"
}
Run the terraform init
command and apply to deploy three aws_instance
resources.
You can verify the creation of these instances by checking the AWS Management Console.
Using a Loop to Deploy AWS VPC
You might need to deploy multiple resources, such as VPCs and subnets, for specific environments. In such a scenario, using the for_each
loop is effective as it helps you save time and reduce extra lines of code while maintaining configuration DRY and consistent.
First, define the ‘vpc_names’ variable and ‘environments’ locals in the var.tf file:
variable "vpc_names" {
type = map(string)
default = {
"vpc-prod" = "Production VPC"
"vpc-dev" = "Staging VPC"
}
}
locals {
environments = {
prod = {
vpc_cidr_block = "10.0.0.0/16"
subnet_cidr_blocks = ["10.0.1.0/24", "10.0.2.0/24"]
}
dev = {
vpc_cidr_block = "10.1.0.0/16"
subnet_cidr_blocks = ["10.1.1.0/24", "10.1.2.0/24"]
}
}
}
Next, create the aws_vpc
resource using for_each
by iterating over the list of vpc_names
and deploying VPCs for each environment in main.tf file.
The cidr_block
is dynamically assigned based on the environment, ensuring the correct IP range is used, like so:
resource "aws_vpc" "network_vpcs" {
for_each = var.vpc_names
cidr_block = local.environments[replace(each.key, "vpc-", "")].vpc_cidr_block
}
After creating VPCs, create aws_subnet
within the appropriate VPCs using for_each
to iterate over subnets in the ‘subnet_map’.
Also, the values for cidr_block
and availability_zone
are dynamically calculated to ensure that subnets are correctly configured for the respective environment.
locals {
subnet_map = {
for vpc_name, vpc_info in local.environments :
vpc_name => {
for i, subnet_cidr in vpc_info.subnet_cidr_blocks :
"${vpc_name}-subnet-${i + 1}" => {
cidr_block = subnet_cidr
vpc_id = aws_vpc.network_vpcs["vpc-${vpc_name}"].id
}
}
}
}
data "aws_availability_zones" "available" {}
resource "aws_subnet" "network_subnets" {
for_each = merge(local.subnet_map["prod"], local.subnet_map["dev"])
vpc_id = each.value.vpc_id
cidr_block = each.value.cidr_block
availability_zone = element(data.aws_availability_zones.available.names, index(local.environments[split("-", each.key)[0]].subnet_cidr_blocks, each.value.cidr_block))
map_public_ip_on_launch = true
tags = {
Name = each.key
Environment = split("-", each.key)[0]
}
}
Run the terraform init
and apply to deploy your infrastructure in AWS.
Once the deployment is complete, you can view the created VPCs and subnets in the AWS Management Console.
Best Practices for Using Terraform for Loop
In the above examples, we learned to use the Terraform for loop. Now, let’s learn some of the best practices you should follow while using the Terraform loop:
- Avoid nested loops: Using nested loops makes it harder to read and maintain your code. Instead, use a single loop in each resource block to simplify your code. For example,
count
to deploy multiple EC2 instances. - Use clear naming conventions: Ensure that your keys and variable names reveal their purpose for an easier understanding of the code. For instance, use
instance_types
instead of a generic name likex
. - Use conditional logic when required: Use ‘if’ clauses with loops to control resource creation. This ensures that you do not create unnecessary resources. For example, create a list of teams that have enabled VPC resource creation using
for
expression. - Prefer using for_each over count: If you want to deploy the same resources with different configurations, use
for_each
. For example, multiple EC2 instances can be deployed with different AMI and instance types using thefor_each
loop.
Using Terraform for_each Loop with env0
With env0, you can deploy multiple resources using centralized variable management, reducing repetitive code and saving time.
Specifically, the platform allows you to automate the deployment process, manage environment variables, and handle deployments efficiently without the need to repeatedly write the same configurations.
In this section, we’ll walk you through setting up and using Terraform loops with env0’s variables to deploy multiple AWS S3 buckets, each customized for a specific environment.
Step 1: Define the provider configuration
Set up your Terraform configuration by defining the AWS provider in your providers.tf file.
provider "aws" {
region = var.region
access_key = var.access_key
secret_key = var.secret_key
}
Step 2: Define variables
Define the ‘region’, ‘access_key’, ‘secret_key’, and ‘environment’ variables in the var.tf file. Additionally, create a ‘bucket_count’ variable to specify the exact number of buckets to be created.
These variables will help you customize and manage your infrastructure for different environments, like development or production.
variable "region" {
description = "AWS region"
type = string
}
variable "access_key" {
description = "AWS access key"
type = string
}
variable "secret_key" {
description = "AWS secret key"
type = string
}
variable "environment" {
description = "Environment type (dev or prod)"
type = string
default = "dev"
}
variable "bucket_count" {
description = "Number of AWS S3 buckets to create"
type = number
default = 2 # Default to 2 buckets for the dev environment
}
Step 3: Define local values
Let's define bucket_names
to dynamically generate unique, environment-specific names for the AWS S3 buckets based on the environment and the ‘bucket_count’.
locals {
bucket_names = [for idx in range(var.bucket_count) : "${var.environment}-infrasity-bucket-${idx + 1}"]
}
Step 4: Create AWS S3 buckets using for_each loop
Use the for_each
loop to create the aws_s3_bucket
resources by iterating over the bucket_names
, and creating each bucket accordingly.
resource "aws_s3_bucket" "infrasity" {
for_each = { for idx, name in local.bucket_names : idx => name }
bucket = each.value
tags = {
Name = each.value
Environment = var.environment
}
}
Step 5: Setup env0
Now that your Terraform configuration is set up, we must integrate our repository with env0. Follow these steps:
- Create a new environment - To apply your IaC configurations, you must create an environment in env0 under your project.
- Connect your GitHub repository - Select VCS integration to create a new environment and attach your github repository link.
- Configure environment variables - You can create the environment variables that are accessible across the environment and required within your Terraform configuration by following these steps:
- Navigate to your ‘environment’ in env0>‘Settings’>’Environment Variables’
- Add the variables:some text
- region: Set your desired AWS region (e.g., ap-south-1)
- access_key: Stores your AWS access key
- secret_key: Stores your AWS secret key
- environment: Specify the environment type, either ‘dev’ or ‘prod’
- bucket_count: Define the number of s3 buckets to create (e.g., 2 for dev, 4 for prod)
Step 6: Deploy the environment setup
Deploy your environment from the env0 dashboard. Click on run-deploy to start the deployment process or just save the changes in the environment variables. Once it begins, you must approve the creation of AWS resources in the terraform plan
section. Once approved, the deployments will be done.
After the successful deployment, you can see three AWS S3 buckets were created in the dev environment.
With env0, you can manage your Terraform environments, apply changes, and monitor your infrastructure. env0’s environment-based management features work seamlessly with Terraform loops, enabling you to efficiently deploy resources and manage your infrastructure across different environments.
Conclusion
By now, you should have a clear understanding of for_each
, count
, and for
and their use cases. We learned how to leverage loops to create multiple resources individually or in a complex structure using conditional logic, as well as best practices.
We also discussed how you can use env0’s centralized variables capability with loops to manage infrastructure effectively.
Frequently Asked Questions
Q. What is Terraform for loop?
Terraform for loop helps you create multiple resources by iterating over lists or maps. The key benefits of using loops are that they help you avoid extra lines of Terraform code and save time and effort.
Q. What is a loop with a count in Terraform?
A count
loop in Terraform is a way to create multiple resources with the same configuration using a single block of code.
Q. What is the difference between count and for_each in Terraform?
A count
is used to create a specific number of resources with exactly the same configuration. On the other hand, for_each
is used to create resources based on items in a list or map, allowing for more customized configurations.
Q. How to iterate a list in Terraform?
You can use a for
expression within a for_eachloop
or when defining local variables to process each item in the list and apply it to resource creation or variable assignment.
Top comments (0)