Skip to content

Dynamic Blocks

Many Terraform resources contain repeated nested blocks — for example, ingress rules inside a security group or tags on multiple resources. Writing each block by hand quickly becomes tedious and error-prone, especially when the values come from a variable. Dynamic blocks solve this by generating nested blocks programmatically from a collection.


A dynamic block acts as a repeatable nested block. It takes a label that matches the nested block type you want to generate, a for_each argument that provides the collection to iterate over, and a content block that defines the body of each generated block.

resource "example_resource" "this" {
name = "example"
dynamic "setting" {
for_each = var.settings
content {
key = setting.value.key
value = setting.value.value
}
}
}

By default, the temporary variable inside content uses the label name — setting in the example above. You can override this with the optional iterator argument, which is useful when nesting dynamic blocks or when a more descriptive name improves readability:

dynamic "setting" {
for_each = var.settings
iterator = s
content {
key = s.value.key
value = s.value.value
}
}

With iterator = s, you reference each element as s.key and s.value instead of setting.key and setting.value.

Practical Example: Security Group Ingress Rules

Section titled “Practical Example: Security Group Ingress Rules”

A common use case is generating AWS security group rules from a variable list. Instead of writing a separate ingress block for every port, you can define the ports in a variable and let a dynamic block do the work.

variable "ingress_rules" {
type = list(object({
port = number
protocol = string
cidr_blocks = list(string)
}))
default = [
{ port = 80, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] },
{ port = 443, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] },
{ port = 22, protocol = "tcp", cidr_blocks = ["10.0.0.0/8"] },
]
}
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}

When Terraform evaluates this configuration, it produces one ingress block for each item in var.ingress_rules. Adding or removing a port is now a single change to the variable rather than a manual edit of the resource block.

Dynamic blocks can be nested when a resource has multiple levels of repeated structure. For example, a CloudFront distribution can have several origin groups, each containing its own set of origins:

variable "origin_groups" {
type = list(object({
group_id = string
origins = list(object({
origin_id = string
domain_name = string
}))
}))
}
resource "aws_cloudfront_distribution" "cdn" {
# ... other configuration ...
dynamic "origin_group" {
for_each = var.origin_groups
content {
origin_id = origin_group.value.group_id
dynamic "member" {
for_each = origin_group.value.origins
content {
origin_id = member.value.origin_id
}
}
}
}
}

Notice that the inner dynamic "member" block iterates over the origins list from the current outer element. Using distinct labels (origin_group and member) keeps the references clear — you could also use the iterator argument if the default names are ambiguous.

Dynamic blocks are powerful, but overusing them can make your configuration harder to read and maintain. HashiCorp’s own guidance recommends using dynamic blocks only when you need to hide details in reusable modules, not as a general tool to wrap every nested block.

Try to write configurations that are easy for a future reader to understand. A few hand-written blocks are often clearer than a dynamic block driven by a complex expression. If you find yourself nesting multiple levels of dynamic blocks or building elaborate local values just to feed into for_each, consider whether a simpler approach — or splitting the resource into a module — would be more maintainable.

Dynamic blocks let you generate repeated nested blocks from a collection, keeping your configuration concise when the number of blocks is driven by a variable. They work well for common patterns like security group rules and multi-level resource structures. Use them where they genuinely reduce duplication, but keep readability in mind — straightforward configuration is always easier to maintain.