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.
Dynamic Block Syntax
Section titled “Dynamic Block Syntax”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.
Nested Dynamic Blocks
Section titled “Nested Dynamic Blocks”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.
When Not to Use Dynamic Blocks
Section titled “When Not to Use Dynamic Blocks”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.
Conclusion
Section titled “Conclusion”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.