If you’ve ever found yourself stuck copy-pasting the same Terraform code just to make small changes, you know how frustrating it can be. Even worse, those repetitive updates often lead to mistakes that are hard to catch.
What if you didn’t have to do any of that? Imagine writing just a few lines of code and letting Terraform handle the rest. That’s exactly what dynamic blocks do for you—they eliminate duplication and manual tweaking, making your configurations cleaner, more efficient, and easier to scale.
So whether you're managing security rules or spinning up multiple instances, dynamic blocks help you work smarter, not harder. And by the end of this guide, you’ll know how to use dynamic blocks to streamline your Terraform workflow as a DevOps Engineer.
So, let’s get into it…
Sidenote: Want to dive deeper into learning and using Terraform? Then check out my DevOps Bootcamp for Terraform.
I cover everything from the fundamentals all the way to provisioning real-world cloud infrastructure on AWS. You can go from total beginner to being able to get hired as a DevOps Engineer or System Administrator, so it’s a great resource for any skill level.
With that out of the way, let’s get into this guide…
Dynamic blocks in Terraform automate repetitive tasks, making your code simpler to manage. Instead of copying and pasting similar blocks over and over, dynamic blocks allow you to loop through a set of values (like a list or map) and automatically generate the necessary configurations.
Dynamic blocks are used exclusively within resource blocks, allowing you to dynamically generate multiple nested configurations inside a resource.
Think of them like a programming for
loop. You define a template, and Terraform loops through the data, applying the same logic across multiple resources.
This makes dynamic blocks especially useful for configurations that scale, such as security group rules or multiple instances.
for_each
: Tells Terraform how many times to repeat the block. It loops through a collection (like a list or map) and creates a block for each entry. for_each
iterates over a collection, generating one instance of the content block per item, making it particularly useful for handling multiple resources efficiently.content
: Defines the structure of each block. It’s the template Terraform uses during each loopingress_rules
: A list of objects where each object defines parameters such as from_port
, to_port
, protocol
, and cidr_blocks
Let’s break it down with an example of managing AWS security group rules:
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
}
}
In this example, Terraform dynamically generates the ingress
block based on the ingress_rules
variable. By looping through the list of rules, Terraform applies the configuration for each one, reducing manual repetition and keeping your code consistent.
This is particularly useful when managing complex infrastructure where the same logic applies to multiple resources.
Dynamic blocks don’t just save you from repetitive tasks - they simplify infrastructure management, especially at scale.
For example
As your infrastructure grows, manually updating configurations across environments (like development, staging, and production) becomes tedious and error-prone. One small change can mean dozens of manual edits, which opens the door to inconsistencies and mistakes.
Dynamic blocks let you define your infrastructure once and apply it across environments, and you no longer have to worry about manually updating files or chasing down configuration errors.
This is particularly helpful in cloud environments like AWS, where managing multiple security groups, subnets, or EC2 instances across regions is common.
In short, dynamic blocks allow:
Now let’s see dynamic blocks in action by creating subnets across multiple availability zones (AZs). Without dynamic blocks, you’d have to manually define each subnet, which becomes error-prone as your infrastructure scales.
But with dynamic blocks, you can automate the creation of subnets by having Terraform loop through a list of subnets and dynamically generate the configuration for each AZ, like so:
variable "subnets" {
type = list(object({
cidr_block = string
az = string
}))
}
resource "aws_subnet" "example" {
for_each = { for idx, subnet in var.subnets : idx => subnet }
cidr_block = each.value.cidr_block
availability_zone = each.value.az
vpc_id = var.vpc_id
}
In this example, the for_each
loop dynamically creates a subnet for each availability zone in your list.
As your infrastructure grows, you can easily add or remove subnets by adjusting the input list—without having to rewrite your entire configuration. This approach keeps your infrastructure scalable and reduces the chance for errors, especially in production environments.
Dynamic blocks can be a huge asset in Terraform, especially when you’re working on complex infrastructure.
To get the most out of them though, it’s important to follow a few key best practices. These will help ensure your code remains clean, scalable, and easy to manage.
One of the most common ways to use dynamic blocks is when working with collections like lists or maps. By looping through a collection, you can apply consistent configurations across multiple resources.
This is especially useful when you’re setting up things like security group rules, EC2 instances, or other resources that require similar settings.
For example
Instead of manually defining multiple EC2 instances, dynamic blocks allow you to loop through a list and apply the configuration for each instance:
resource "aws_instance" "example" {
for_each = { for idx, instance in var.instances : idx => instance }
instance_type = each.value.instance_type
ami = each.value.ami
}
This not only reduces repetition but also makes scaling your infrastructure easier, as Terraform handles the creation of each resource consistently.
Modules are an essential feature in Terraform, and dynamic blocks shine when used inside them. Instead of duplicating similar blocks across different projects or environments, you can create reusable modules that use dynamic blocks to handle collections of resources.
For instance, if you manage multiple environments—like development, staging, and production—you can define a module that provisions resources for all environments, passing environment-specific variables each time you call the module.
This approach helps keep your infrastructure DRY, reduces potential errors, and makes the code easier to maintain.
By using dynamic blocks within reusable modules, you ensure your configurations are scalable and adaptable across different environments.
Although dynamic blocks can simplify your code, it’s important not to overuse them. A best practice is to keep your dynamic blocks as simple and readable as possible. If adding a dynamic block doesn’t simplify the code or is making it harder to understand, it might be better to use static blocks.
For example, if a resource doesn’t need to be repeated across multiple items in a collection, using a static block will keep the code simpler. Always prioritize readability and simplicity when designing your Terraform configurations.
Another best practice is to document how and why dynamic blocks are used in your configuration. Even though dynamic blocks can automate a lot, they might be unfamiliar to some team members or future readers of your code.
By adding comments that explain how the dynamic block works, what variables it relies on, and what outcomes are expected, you make the code more maintainable and easier for others to follow.
For example
# Dynamic block for ingress rules
# Loops through ingress_rules and applies settings for each
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
}
}
Proper documentation makes it easier for others (or even your future self) to understand the purpose of the dynamic block and how it functions.
While dynamic blocks can simplify Terraform configurations, there are several common mistakes you should be aware of.
Avoiding these will help you prevent errors and keep your configurations clean and maintainable.
It’s tempting to use dynamic blocks everywhere because of how they reduce repetitive code. However, sometimes static blocks are the better option, particularly if the dynamic block doesn’t add much benefit and only makes the code harder to read.
Remember that simplicity is key. If using dynamic blocks makes your configuration more complicated than it needs to be, opt for static blocks instead.
Dynamic blocks combined with too many conditional (if
) statements can quickly turn into a maintenance nightmare. Each condition adds complexity, making it harder to debug and maintain the configuration.
If you find yourself adding a lot of conditional logic within dynamic blocks, it might be time to refactor or simplify your configuration by splitting it into smaller, more manageable pieces.
One of the most common errors with dynamic blocks is a data type mismatch. For example, if for_each
expects a list but you accidentally provide a map, Terraform will throw an error.
To avoid this, always clearly define your variables and ensure the data types are correct.
For example
Here’s how you can define ingress rules with the correct data types:
variable "ingress_rules" {
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
}
Explicitly defining data types helps avoid mismatches and ensures smooth execution of dynamic blocks.
If your for_each
loop encounters an empty list or a null value, Terraform might skip the dynamic block entirely, which can lead to incomplete configurations.
To prevent this, ensure you handle empty lists or null values in your code by providing a fallback option.
dynamic "ingress" {
for_each = var.ingress_rules != null ? 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
}
}
This ensures that even if the list is empty, Terraform doesn’t break or skip necessary parts of the configuration.
terraform plan
Before applying any changes, always run terraform plan
. This command shows you what Terraform is about to do, helping you catch potential mistakes early.
If you find yourself stuck on an error, verbose logging can be helpful for tracking down the issue:
TF_LOG=DEBUG terraform apply
This will give you detailed logs that can help pinpoint where things went wrong, especially when dealing with complex dynamic blocks.
By now, you should have a solid understanding of how dynamic blocks can make your life easier. Whether you’re spinning up subnets, managing security groups, or scaling instances, dynamic blocks help you reduce repetition, avoid mistakes, and keep your code clean and flexible.
The best part is, they save you time and headaches as your infrastructure grows.
Just remember though to always follow best practices, watch out for common pitfalls like type mismatches, and always preview your changes with terraform plan
. Do that, and dynamic blocks will quickly become one of your go-to tools in Terraform.
Now it’s time to put what you’ve learned into practice. Take a look at your Terraform setup and see where dynamic blocks can simplify things. The more you use them, the more efficient your infrastructure management will be.
Don’t forget! If you want to dive deeper into learning and using Terraform, check out my DevOps Bootcamp for Terraform.
I cover everything from the fundamentals all the way to provisioning real-world cloud infrastructure on AWS and getting hired!
Once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students and working professionals.
Not only that, but as a member - you’ll also get access to every course in the Zero To Mastery library!