Getty Images

Simplify code with for_each and dynamic blocks in Terraform

In this tutorial, get hands-on practice using Terraform features like dynamic blocks and the for_each attribute to write cleaner, more reusable code for cloud deployments.

Terraform is a powerful infrastructure-as-code tool you can use to automate the creation, maintenance and destruction of cloud resources. Similar to how developers use repositories like GitHub to store their code, Terraform offers the benefits of a version control system for an organization's cloud infrastructure.

Terraform is also open source and under constant maintenance. Because it's regularly updated by many developers, Terraform is always evolving and adding new features. You can even submit a feature of your own to add something you think would benefit the community.

Terraform offers several features that encourage programming best practices, such as code reuse and dynamic code blocks. In this tutorial, you'll explore Terraform's versatility as an infrastructure-as-code tool by learning how to work with two highly useful features: the for_each attribute and dynamic blocks.

How to use Terraform's for_each attribute

Normally a Terraform project's code is paired with an application, with the Terraform code controlling the infrastructure that the application runs on. In this design paradigm, the specific infrastructure is the same across applications and environments in many instances.

To simplify the process of iteratively creating the same cloud resource multiple times, Terraform introduced the for_each argument. Passing the for_each argument to a cloud resource tells Terraform to create a different resource for each item in a map or list.

Example: Using for_each to create a VPC resource

To illustrate, use a Terraform module to create a simple virtual private cloud (VPC) resource in AWS, as shown in Figure 1.

Screenshot of code that creates a VPC resource in AWS using the Terraform VPC module.
Figure 1. Creating a simple VPC resource in AWS with a Terraform module.

To create the same resource for multiple environments, start by adding a map of the different environments to create along with the values to be used for each environment, as shown in Figure 2.

Screenshot of code that defines a variable called
Figure 2. Defining a new environment variable to create the same resource for multiple environments, such as dev, staging and production.

The environment variable is a map of different maps, which you can then reference in the main Terraform file, as shown in Figure 3.

Screenshot of the VPC module first shown in Figure 1. There is now a new line of code setting the value of for_each to var.environment.
Figure 3. Referencing the environment variable in the main Terraform file introduced in Figure 1.

Adding the for_each attribute on line 14 and setting its value to the environment variable tells Terraform to create a VPC module for each item in the environment variable map.

Lines 8 and 9 reference the private and public subnet lists contained in the map of each environment using the syntax each.value.<list_name>. In other words, for each item in the environment map, get the value of that item and then get the item by name.

Benefits of using for_each in Terraform

Using the for_each attribute can cut down on unnecessary duplication in Terraform configurations. Adding new items to the environment map or modifying existing items is quick because all the main configurable items are stored in one place.

You can use other patterns to accomplish the same setup as in this example, such as using Terraform workspaces. However, for_each can be applied more broadly to dynamically create resources depending on lists, maps or other resources.

How to use dynamic blocks in Terraform

Speaking of dynamically creating resources, let's take a look at another of Terraform's most useful features: dynamic blocks.

In Terraform, dynamic blocks let you create nested blocks inside a resource based on a variable. Instead of creating a resource for each item in a map, as the for_each attribute does, dynamic blocks create nested blocks inside a resource for each item in a map or list.

Example: Using dynamic blocks to simplify ingress and egress rules

The easiest way to understand how to use dynamic blocks is through an example.

Figure 4 shows an AWS security group resource. This security group has a few ingress and egress rules to control access to another resource within the VPCs created in the for_each example prior.

Screenshot of code that defines a security group resource in AWS, including sets of ingress and egress rules.
Figure 4. Defining an AWS security group resource with ingress and egress rules.

As Figure 4 shows, there's quite a bit of duplication, which means an opportunity to clean up this repetition with Terraform's dynamic blocks.

After converting the ingress and egress rules to dynamic blocks, the Terraform code should look like Figure 5.

Screenshot of code that defines the same AWS security group resource. The program is now simpler and shorter due to using dynamic blocks.
Figure 5. Simplifying ingress and egress rule definitions for the same AWS security group using dynamic blocks.

The dynamic blocks contain the for_each attribute, which is assigned to different variables now. Figure 6 shows the definitions of the variables used.

Screenshot of code that sets values of the security group's ingress and egress variables to open incoming and outgoing ports, respectively.
Figure 6. Defining ingress and egress variable values for the same AWS security group.

Note that the name of the dynamic block is not simply a string. It must match the name of the final block you intend to create.

Next, the dynamic block creates final blocks based on the values in the map or list specified by the for_each attribute. In concept this is very similar to the for_each attribute acting on a resource.

Each value from the map or list is used to define values in the content of the final ingress or egress block. If the map or list is empty, Terraform does not create any final block. This feature can be useful when implemented as a mechanism to disable or enable certain features of a resource controlled by blocks defined in that resource.

Benefits of using dynamic blocks

Overall, using dynamic blocks keeps your Terraform code more concise and maintainable, reducing the amount of rework needed with configuration changes.

Continuing with the prior example, say you need to add a similar new ingress rule to your security group. With a configuration that uses dynamic blocks, you only need to add a new value of the port to the ingress variable list.

Next Steps

A Terraform Registry tutorial to build and share modules

How to deploy an EKS cluster using Terraform

 How to create and deploy AWS Lambda functions with Terraform

Dig Deeper on DevOps