0
0
Terraformcloud~7 mins

Dynamic blocks vs for_each decision in Terraform - CLI Comparison

Choose your learning style9 modes available
Introduction
When writing Terraform code, you often need to create multiple similar resources or nested blocks. Dynamic blocks and for_each loops help automate this, but choosing the right one makes your code simpler and easier to manage.
When you want to create multiple nested blocks inside a resource based on a list or map.
When you need to create multiple separate resources or modules from a collection of items.
When your nested blocks have complex structures that vary per item.
When you want to avoid repeating similar blocks manually in your configuration.
When you want to control resource creation with keys or indexes for better tracking.
Config File - main.tf
main.tf
variable "security_groups" {
  type = list(object({
    name        = string
    description = string
    ingress     = list(object({
      from_port   = number
      to_port     = number
      protocol    = string
      cidr_blocks = list(string)
    }))
  }))
  default = [
    {
      name        = "web-sg"
      description = "Allow web traffic"
      ingress = [
        {
          from_port   = 80
          to_port     = 80
          protocol    = "tcp"
          cidr_blocks = ["0.0.0.0/0"]
        }
      ]
    },
    {
      name        = "ssh-sg"
      description = "Allow SSH"
      ingress = [
        {
          from_port   = 22
          to_port     = 22
          protocol    = "tcp"
          cidr_blocks = ["192.168.1.0/24"]
        }
      ]
    }
  ]
}

resource "aws_security_group" "example" {
  for_each = { for sg in var.security_groups : sg.name => sg }

  name        = each.value.name
  description = each.value.description

  dynamic "ingress" {
    for_each = each.value.ingress
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

This Terraform file defines a variable with a list of security groups, each having ingress rules.

The aws_security_group resource uses for_each to create one security group per item in the list, keyed by the group's name.

Inside the resource, a dynamic block creates multiple ingress blocks based on the ingress rules for each security group.

This combination shows when to use for_each for resources and dynamic blocks for nested blocks.

Commands
Initializes Terraform in the current directory, downloading required providers and preparing the environment.
Terminal
terraform init
Expected OutputExpected
Initializing the backend... Initializing provider plugins... - Finding latest version of hashicorp/aws... - Installing hashicorp/aws v4.0.0... - Installed hashicorp/aws v4.0.0 (signed by HashiCorp) Terraform has been successfully initialized!
Shows the execution plan, detailing what resources Terraform will create based on the configuration.
Terminal
terraform plan
Expected OutputExpected
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_security_group.example["ssh-sg"] will be created + resource "aws_security_group" "example" { + description = "Allow SSH" + name = "ssh-sg" + ... + ingress { + cidr_blocks = ["192.168.1.0/24"] + from_port = 22 + protocol = "tcp" + to_port = 22 } } # aws_security_group.example["web-sg"] will be created + resource "aws_security_group" "example" { + description = "Allow web traffic" + name = "web-sg" + ... + ingress { + cidr_blocks = ["0.0.0.0/0"] + from_port = 80 + protocol = "tcp" + to_port = 80 } } Plan: 2 to add, 0 to change, 0 to destroy.
Applies the planned changes, creating the security groups and their ingress rules automatically without asking for confirmation.
Terminal
terraform apply -auto-approve
Expected OutputExpected
aws_security_group.example["ssh-sg"]: Creating... aws_security_group.example["web-sg"]: Creating... aws_security_group.example["ssh-sg"]: Creation complete after 2s [id=sg-0123456789abcdef0] aws_security_group.example["web-sg"]: Creation complete after 2s [id=sg-0fedcba9876543210] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
-auto-approve - Skip interactive approval prompt
Displays the current state of the deployed infrastructure, confirming the security groups and their ingress rules exist.
Terminal
terraform show
Expected OutputExpected
... # aws_security_group.example["ssh-sg"]: resource "aws_security_group" "example" { id = "sg-0123456789abcdef0" name = "ssh-sg" description = "Allow SSH" ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["192.168.1.0/24"] } } # aws_security_group.example["web-sg"]: resource "aws_security_group" "example" { id = "sg-0fedcba9876543210" name = "web-sg" description = "Allow web traffic" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } }
Key Concept

Use for_each to create multiple resources from a collection and dynamic blocks to generate multiple nested blocks inside a resource based on complex data.

Common Mistakes
Using for_each inside a resource to create nested blocks instead of dynamic blocks
Terraform does not support for_each directly on nested blocks; it causes syntax errors.
Use dynamic blocks with for_each inside the resource to generate nested blocks.
Using dynamic blocks to create multiple resources instead of nested blocks
Dynamic blocks only work inside a resource for nested blocks, not for creating separate resources.
Use for_each at the resource level to create multiple resources.
Not keying for_each with unique keys when creating multiple resources
Terraform requires unique keys for for_each to track resources properly; using a list without keys can cause issues.
Convert the list to a map with unique keys, such as resource names, before using for_each.
Summary
Use for_each on resources to create multiple instances from a map or set of unique keys.
Use dynamic blocks inside resources to generate multiple nested blocks based on complex data structures.
Combining for_each and dynamic blocks helps write clean, reusable Terraform code for complex infrastructure.