Terraform dynamic block | Brainboard Blog

Terraform dynamic block

Chafik Belhaoues August 20, 2025
12 min read
Expert reviewed
Terraform dynamic block
Terraform dynamic blockMaster the art of writing cleaner, more maintainable Infrastructure as Code with Terraform dynamic blocks. Learn how to eliminate repetition and create flexible configurations.DevOpsChafik Belhaoues2025-08-20T00:00:00.000Z12 minutesintermediateguidedevelopers, DevOps engineers, cloud architects

Stay DRY

Have you ever found yourself copying and pasting the same block of Terraform configuration multiple times, only changing a few values? This repetitive approach not only makes your Infrastructure as Code (IaC) harder to maintain but also increases the likelihood of errors creeping into your configurations. Today, we’ll explore how Terraform’s dynamic blocks can revolutionize the way you write infrastructure code, making it more maintainable, readable, and DRY (Don’t Repeat Yourself).

What are Terraform dynamic blocks?

Dynamic blocks in Terraform are a powerful feature that allows you to generate multiple nested blocks within a resource or module based on a collection of values. Think of them as a programmatic way to create repeating configuration blocks without manually writing each one. Instead of hardcoding multiple similar blocks, you can use a single dynamic block that iterates over a list or map, generating the necessary configuration automatically.

When you’re managing infrastructure across multiple regions like North America, Europe, or Asia-Pacific, dynamic blocks become particularly valuable. They enable you to create region-specific configurations without duplicating code for each geographical location. This approach is especially beneficial for organizations operating in cities like New York, London, Tokyo, or Singapore, where compliance and data residency requirements might necessitate similar but slightly different infrastructure patterns.

Why should you use dynamic blocks?

The primary advantage of using dynamic blocks lies in their ability to reduce code duplication significantly. When you’re managing resources that require multiple similar nested blocks, such as security group rules, load balancer listener rules, or storage account network rules, dynamic blocks can transform dozens of lines of repetitive code into a concise, maintainable solution.

Consider a scenario where you need to create multiple ingress rules for an AWS security group. Without dynamic blocks, you’d write separate ingress blocks for each rule, potentially resulting in hundreds of lines of configuration. With dynamic blocks, you can define your rules in a variable and let Terraform generate the necessary blocks automatically. This approach not only saves time but also makes it easier to update configurations when requirements change.

Moreover, dynamic blocks enhance the reusability of your Terraform modules. You can create flexible modules that accept various inputs and generate appropriate configurations based on those inputs, making your infrastructure code more adaptable to different environments and use cases.

Understanding the syntax and structure

The syntax of a dynamic block might seem intimidating at first, but once you understand its components, you’ll find it quite intuitive. Let’s break down the structure:

dynamic "block_name" {
  for_each = var.collection
  content {
    attribute1 = block_name.value.property1
    attribute2 = block_name.value.property2
  }
}

The dynamic keyword signals to Terraform that you’re creating a dynamic block. The block name corresponds to the type of nested block you want to generate (such as “ingress” for security group rules). The for_each argument specifies the collection you want to iterate over, which can be a list, set, or map. Inside the content block, you define the attributes for each generated block, accessing values through the iterator object.

Practical examples and use cases

Example 1: AWS security group with multiple ingress rules

Let’s start with a common scenario: configuring an AWS security group with multiple ingress rules. Instead of writing separate ingress blocks for each rule, we can use a dynamic block:

variable "ingress_rules" {
  type = list(object({
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
    description = string
  }))
  default = [
    {
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTP from anywhere"
    },
    {
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTPS from anywhere"
    },
    {
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
      cidr_blocks = ["10.0.0.0/8"]
      description = "SSH from internal network"
    }
  ]
}

resource "aws_security_group" "example" {
  name        = "example-security-group"
  description = "Security group with dynamic ingress rules"
  vpc_id      = var.vpc_id

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
      description = ingress.value.description
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow all outbound traffic"
  }
}

Example 2: Azure storage account with network rules

For organizations operating in regions like Western Europe, East US, or Southeast Asia, managing storage account network rules efficiently becomes crucial. Here’s how dynamic blocks can help:

variable "allowed_subnets" {
  type = map(string)
  default = {
    "subnet1" = "/subscriptions/xxx/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/subnet1"
    "subnet2" = "/subscriptions/xxx/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/subnet2"
    "subnet3" = "/subscriptions/xxx/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/subnet1"
  }
}

variable "allowed_ip_ranges" {
  type = list(string)
  default = [
    "192.168.1.0/24",
    "10.0.0.0/16",
    "172.16.0.0/12"
  ]
}

resource "azurerm_storage_account" "example" {
  name                     = "examplestorageaccount"
  resource_group_name      = azurerm_resource_group.example.name
  location                 = azurerm_resource_group.example.location
  account_tier             = "Standard"
  account_replication_type = "LRS"

  network_rules {
    default_action = "Deny"

    dynamic "virtual_network_subnet_ids" {
      for_each = var.allowed_subnets
      content {
        id = virtual_network_subnet_ids.value
      }
    }

    ip_rules = var.allowed_ip_ranges
  }
}

Advanced techniques and best practices

When working with dynamic blocks, there are several advanced techniques that can make your code even more powerful and maintainable. One such technique involves using conditional logic within dynamic blocks. You can combine the for_each argument with conditional expressions to filter which blocks get created:

dynamic "ingress" {
  for_each = { for rule in var.ingress_rules : rule.description => rule if rule.enabled }
  content {
    from_port   = ingress.value.from_port
    to_port     = ingress.value.to_port
    protocol    = ingress.value.protocol
    cidr_blocks = ingress.value.cidr_blocks
    description = ingress.key
  }
}

Another powerful pattern involves nesting dynamic blocks. While this should be used judiciously to maintain readability, it can be incredibly useful for complex configurations:

dynamic "setting" {
  for_each = var.settings
  content {
    namespace = setting.value.namespace
    name      = setting.value.name
    value     = setting.value.value

    dynamic "option" {
      for_each = setting.value.options
      content {
        key   = option.value.key
        value = option.value.value
      }
    }
  }
}

Common pitfalls and how to avoid them

While dynamic blocks are powerful, they come with their own set of challenges. One common mistake is overusing them, which can make your code harder to understand. Remember, just because you can use a dynamic block doesn’t mean you always should. If you’re only creating one or two blocks, it might be clearer to write them explicitly.

Another pitfall involves the iterator variable. By default, the iterator uses the block name, but you can customize it for clarity:

dynamic "ingress" {
  for_each = var.ingress_rules
  iterator = rule
  content {
    from_port = rule.value.from_port
    to_port   = rule.value.to_port
    # ... other attributes
  }
}

Performance considerations also come into play when using dynamic blocks with large datasets. If you’re iterating over hundreds or thousands of items, consider whether your approach might impact Terraform’s planning and applying phases. Sometimes, breaking down large configurations into smaller, more manageable modules can improve both performance and maintainability.

Testing and debugging dynamic blocks

Testing dynamic blocks requires a slightly different approach than testing static configurations. You’ll want to verify that your dynamic blocks generate the expected number of resources and that each resource has the correct configuration. The terraform plan command is your best friend here, as it shows you exactly what Terraform will create.

For more complex scenarios, consider using Terraform’s console mode to test your expressions:

terraform console
> { for rule in var.ingress_rules : rule.description => rule }

This interactive mode allows you to experiment with your data structures and transformations before implementing them in your dynamic blocks.

Conclusion

Dynamic blocks in Terraform represent a significant step forward in writing maintainable, scalable Infrastructure as Code. By reducing repetition and increasing flexibility, they enable DevOps teams to manage complex infrastructure configurations more efficiently. Whether you’re managing resources across multiple geographical regions from San Francisco to Mumbai, or simply trying to keep your codebase DRY, dynamic blocks offer a powerful solution. As you incorporate them into your Terraform workflows, remember to balance their power with readability and always consider whether a dynamic block is the right tool for your specific use case.

FAQs

Can I use dynamic blocks with all Terraform resources?

Not all resources support every type of nested block as a dynamic block. Dynamic blocks work with configuration blocks that can be repeated within a resource, such as ingress/egress rules in security groups, but not with single-instance blocks like lifecycle or timeouts.

What’s the difference between for_each at the resource level and dynamic blocks?

Resource-level for_each creates multiple instances of the entire resource, while dynamic blocks create multiple nested blocks within a single resource. Use resource-level for_each when you need multiple separate resources and dynamic blocks when you need multiple configuration blocks within one resource.

How do I handle empty collections in dynamic blocks?

When the collection in for_each is empty, Terraform simply doesn’t generate any blocks. This is actually a feature, as it allows you to conditionally include blocks based on whether your collection has any items.

Can I use count instead of for_each in dynamic blocks?

No, dynamic blocks only support for_each, not count. This is because for_each provides better tracking of individual blocks and makes it easier to handle changes in your configuration without causing unnecessary resource recreation.

Is there a performance impact when using many dynamic blocks?

While dynamic blocks themselves don’t significantly impact performance, very large collections can slow down Terraform’s planning phase. If you’re experiencing performance issues, consider whether your data structures could be optimized or if breaking down your configuration into smaller modules might help.

Chafik Belhaoues

Cloud Architect and Former SRE
LinkedIn

Chafik has more than 20 years of experience in the IT industry, including 12 years as a Senior Site Reliability Engineer (SRE) and 8 years as a Cloud Architect. He is the founder of Brainboard, focusing on cloud infrastructure and DevOps automation.

Back to Blog