Terraform for loop helps you write simplified, repeatable code used to deploy resources. In this article, we’ll explore for loops using [.code]for_each[.code] and [.code]count[.code], 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 [.code]for_each[.code] and [.code]count[.code] meta-arguments. When used together with the [.code]for[.code] 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 [.code]for_each[.code] 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, [.code]for_each[.code] generates an instance for every item, following the DRY principle.
When you use [.code]for_each[.code] in a resource block, Terraform provides each object with [.code]each.key[.code] and [.code]each.value[.code]. 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 [.code]for_each[.code] 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 [.code]for_each[.code] loop iterates over the [.code]instance_map[.code] and dynamically sets the [.code]ami[.code] and [.code]instance_type[.code] for each instance in the map.
For Expression
The [.code]for[.code] expression in Terraform helps you transform a value into a new list or map. Using [.code]for[.code] 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 [.code]if[.code] clause with the [.code]for[.code] 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 [.code]for[.code] 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 [.code]for[.code] is used to iterate over [.code]original_list[.code] 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 [.code]-environment[.code] in each item, creating a new list [.code]transformed_list[.code] with the modified values.
Count Meta-argument
The [.code]count[.code] is used to create an exact number of instances of a resource with the same configuration.
Unlike [.code]for_each[.code], which is ideal for resources with distinct configurations, [.code]count[.code] is simpler and more effective when you need to deploy multiple resources.
Syntax of count meta-argument
To understand how the [.code]count[.code] 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 [.code]count[.code] meta-argument.
To demonstrate how [.code]count[.code] works, let’s create three EC2 instances with the same configuration by setting the [.code]count[.code] 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 [.code]count[.code] 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 [.code]for_each[.code] 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 [.code]for_each[.code] loop to iterate over instances and create two [.code]aws_instance[.code] resources. The configuration value for [.code]ami[.code] and [.code]instance_type[.code] is passed using the [.code]each[.code] 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 [.code]terraform init[.code] and apply commands to deploy the two [.code]aws_instance[.code] resources.
To verify the creation of these [.code]aws_instance[.code] 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 [.code]for[.code] loop to dynamically generate a list of [.code]bucket_names[.code], 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 [.code]for_each[.code] 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 [.code]for_each[.code] loop. But what about when you are required to deploy multiple resources with the same configuration, such as EC2 instances?
In this scenario, using [.code]for_each[.code] 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 [.code]count[.code] is ideal and efficient.
Write the resource block to deploy EC2 instances with [.code]count[.code] 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 [.code]terraform init[.code] command and apply to deploy three [.code]aws_instance[.code] 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 [.code]for_each[.code] 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 [.code]aws_vpc[.code] resource using [.code]for_each[.code] by iterating over the list of [.code]vpc_names[.code] and deploying VPCs for each environment in main.tf file.
The [.code]cidr_block[.code] 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 [.code]aws_subnet[.code] within the appropriate VPCs using [.code]for_each[.code] to iterate over subnets in the ‘subnet_map’.
Also, the values for [.code]cidr_block[.code] and [.code]availability_zone[.code] 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 [.code]terraform init[.code] 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, [.code]count[.code] 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 [.code]instance_types[.code] instead of a generic name like [.code]x[.code].
- 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 [.code]for[.code] expression.
- Prefer using for_each over count: If you want to deploy the same resources with different configurations, use [.code]for_each[.code]. For example, multiple EC2 instances can be deployed with different AMI and instance types using the [.code]for_each[.code] 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 [.code]bucket_names[.code] 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 [.code]for_each[.code] loop to create the [.code]aws_s3_bucket[.code] resources by iterating over the [.code]bucket_names[.code], 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 [.code]terraform plan[.code] 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 [.code]for_each[.code], [.code]count[.code], and [.code]for[.code] 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 [.code]count[.code] 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 [.code]count[.code] is used to create a specific number of resources with exactly the same configuration. On the other hand, [.code]for_each[.code] 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 [.code]for[.code] expression within a [.code]for_each[.code]loop or when defining local variables to process each item in the list and apply it to resource creation or variable assignment.
Terraform for loop helps you write simplified, repeatable code used to deploy resources. In this article, we’ll explore for loops using [.code]for_each[.code] and [.code]count[.code], 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 [.code]for_each[.code] and [.code]count[.code] meta-arguments. When used together with the [.code]for[.code] 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 [.code]for_each[.code] 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, [.code]for_each[.code] generates an instance for every item, following the DRY principle.
When you use [.code]for_each[.code] in a resource block, Terraform provides each object with [.code]each.key[.code] and [.code]each.value[.code]. 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 [.code]for_each[.code] 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 [.code]for_each[.code] loop iterates over the [.code]instance_map[.code] and dynamically sets the [.code]ami[.code] and [.code]instance_type[.code] for each instance in the map.
For Expression
The [.code]for[.code] expression in Terraform helps you transform a value into a new list or map. Using [.code]for[.code] 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 [.code]if[.code] clause with the [.code]for[.code] 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 [.code]for[.code] 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 [.code]for[.code] is used to iterate over [.code]original_list[.code] 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 [.code]-environment[.code] in each item, creating a new list [.code]transformed_list[.code] with the modified values.
Count Meta-argument
The [.code]count[.code] is used to create an exact number of instances of a resource with the same configuration.
Unlike [.code]for_each[.code], which is ideal for resources with distinct configurations, [.code]count[.code] is simpler and more effective when you need to deploy multiple resources.
Syntax of count meta-argument
To understand how the [.code]count[.code] 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 [.code]count[.code] meta-argument.
To demonstrate how [.code]count[.code] works, let’s create three EC2 instances with the same configuration by setting the [.code]count[.code] 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 [.code]count[.code] 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 [.code]for_each[.code] 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 [.code]for_each[.code] loop to iterate over instances and create two [.code]aws_instance[.code] resources. The configuration value for [.code]ami[.code] and [.code]instance_type[.code] is passed using the [.code]each[.code] 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 [.code]terraform init[.code] and apply commands to deploy the two [.code]aws_instance[.code] resources.
To verify the creation of these [.code]aws_instance[.code] 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 [.code]for[.code] loop to dynamically generate a list of [.code]bucket_names[.code], 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 [.code]for_each[.code] 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 [.code]for_each[.code] loop. But what about when you are required to deploy multiple resources with the same configuration, such as EC2 instances?
In this scenario, using [.code]for_each[.code] 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 [.code]count[.code] is ideal and efficient.
Write the resource block to deploy EC2 instances with [.code]count[.code] 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 [.code]terraform init[.code] command and apply to deploy three [.code]aws_instance[.code] 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 [.code]for_each[.code] 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 [.code]aws_vpc[.code] resource using [.code]for_each[.code] by iterating over the list of [.code]vpc_names[.code] and deploying VPCs for each environment in main.tf file.
The [.code]cidr_block[.code] 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 [.code]aws_subnet[.code] within the appropriate VPCs using [.code]for_each[.code] to iterate over subnets in the ‘subnet_map’.
Also, the values for [.code]cidr_block[.code] and [.code]availability_zone[.code] 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 [.code]terraform init[.code] 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, [.code]count[.code] 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 [.code]instance_types[.code] instead of a generic name like [.code]x[.code].
- 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 [.code]for[.code] expression.
- Prefer using for_each over count: If you want to deploy the same resources with different configurations, use [.code]for_each[.code]. For example, multiple EC2 instances can be deployed with different AMI and instance types using the [.code]for_each[.code] 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 [.code]bucket_names[.code] 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 [.code]for_each[.code] loop to create the [.code]aws_s3_bucket[.code] resources by iterating over the [.code]bucket_names[.code], 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 [.code]terraform plan[.code] 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 [.code]for_each[.code], [.code]count[.code], and [.code]for[.code] 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 [.code]count[.code] 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 [.code]count[.code] is used to create a specific number of resources with exactly the same configuration. On the other hand, [.code]for_each[.code] 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 [.code]for[.code] expression within a [.code]for_each[.code]loop or when defining local variables to process each item in the list and apply it to resource creation or variable assignment.