0
0
Terraformcloud~15 mins

Dynamic blocks in security groups in Terraform - Deep Dive

Choose your learning style9 modes available
Overview - Dynamic blocks in security groups
What is it?
Dynamic blocks in security groups are a way to create multiple similar rules in a security group using a loop-like structure in Terraform. Instead of writing each rule manually, dynamic blocks let you generate many rules from a list or map. This makes managing security groups easier and less error-prone, especially when rules change often or are numerous.
Why it matters
Without dynamic blocks, you would have to write each security rule by hand, which is slow and prone to mistakes. This can lead to security holes or overly permissive access. Dynamic blocks solve this by automating rule creation, making your infrastructure safer and easier to update. This saves time and reduces risks in real cloud environments.
Where it fits
Before learning dynamic blocks, you should understand basic Terraform syntax and how security groups work in cloud platforms like AWS. After mastering dynamic blocks, you can explore advanced Terraform features like modules and complex conditionals to build reusable and flexible infrastructure code.
Mental Model
Core Idea
Dynamic blocks let you write one template that repeats many times to create multiple security group rules automatically.
Think of it like...
Imagine you want to send the same invitation to many friends. Instead of writing each letter by hand, you write one letter and use a mail merge to send it to everyone. Dynamic blocks do the same for security rules.
security_group {
  ├─ dynamic "ingress" {
  │    ├─ for_each: list of rules
  │    └─ content: rule template
  └─ other static rules
}

This repeats the ingress block for each rule in the list.
Build-Up - 7 Steps
1
FoundationUnderstanding security groups basics
🤔
Concept: Learn what security groups are and how rules control network access.
Security groups act like a firewall for cloud resources. They have rules that allow or block traffic based on ports, protocols, and IP addresses. Each rule is written explicitly in Terraform as a block inside the security group resource.
Result
You can control who can connect to your servers by defining rules manually.
Knowing how security groups work is essential before automating their rules with dynamic blocks.
2
FoundationWriting static security group rules in Terraform
🤔
Concept: How to define fixed ingress and egress rules in Terraform code.
In Terraform, you write security group rules as nested blocks inside the security group resource. For example: resource "aws_security_group" "example" { name = "example-sg" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } } This creates one rule allowing HTTP traffic from anywhere.
Result
Terraform creates a security group with one fixed rule.
Static rules work but become repetitive and hard to maintain when many rules are needed.
3
IntermediateIntroducing dynamic blocks syntax
🤔Before reading on: do you think dynamic blocks replace entire resources or just parts inside them? Commit to your answer.
Concept: Dynamic blocks generate repeated nested blocks inside a resource using a loop over a collection.
Dynamic blocks use the syntax: dynamic "block_name" { for_each = collection content { # block attributes using each.value } } This tells Terraform to create one nested block for each item in the collection, filling in values dynamically.
Result
Terraform expands the dynamic block into multiple nested blocks based on the collection size.
Understanding that dynamic blocks only generate nested blocks inside a resource helps you use them precisely where repetition is needed.
4
IntermediateApplying dynamic blocks to security group rules
🤔Before reading on: do you think dynamic blocks can handle different ports and CIDR blocks in one list? Commit to your answer.
Concept: Use dynamic blocks to create multiple ingress or egress rules from a list of maps with different ports and IP ranges.
Example: variable "rules" { default = [ { from_port = 80, to_port = 80, protocol = "tcp", cidr = "0.0.0.0/0" }, { from_port = 443, to_port = 443, protocol = "tcp", cidr = "10.0.0.0/16" } ] } resource "aws_security_group" "example" { name = "example-sg" dynamic "ingress" { for_each = var.rules content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } } } This creates two ingress rules from the list.
Result
Terraform creates multiple ingress rules automatically from the variable list.
Dynamic blocks let you manage many rules cleanly and update them by changing just the input list.
5
IntermediateCombining static and dynamic rules safely
🤔
Concept: You can mix static rules with dynamic blocks in one security group resource to handle fixed and variable rules.
Example: resource "aws_security_group" "example" { name = "example-sg" ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["192.168.1.0/24"] } dynamic "ingress" { for_each = var.rules content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } } } This keeps a fixed SSH rule and adds others dynamically.
Result
Terraform creates both the static and dynamic ingress rules together.
Knowing you can combine static and dynamic blocks lets you keep essential rules fixed while flexibly adding others.
6
AdvancedUsing conditionals inside dynamic blocks
🤔Before reading on: can you guess how to skip creating a rule conditionally inside a dynamic block? Commit to your answer.
Concept: You can filter or conditionally include rules by controlling the for_each collection or using conditionals inside content.
Example filtering rules: resource "aws_security_group" "example" { name = "example-sg" dynamic "ingress" { for_each = [for r in var.rules : r if r.enabled] content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } } } Only rules with enabled = true are created.
Result
Terraform creates only the rules that meet the condition, skipping others.
Filtering the input list before the dynamic block gives precise control over which rules are created.
7
ExpertAvoiding common pitfalls with dynamic blocks
🤔Before reading on: do you think dynamic blocks can cause unexpected resource changes if the input list order changes? Commit to your answer.
Concept: Dynamic blocks depend on the order and uniqueness of the for_each collection keys; changing these can cause resource replacement or unexpected diffs.
Terraform tracks nested blocks by their keys. If your for_each is a list, Terraform uses the index as the key. Changing the order or adding/removing items shifts indexes, causing Terraform to destroy and recreate rules unnecessarily. Best practice is to use a map with stable keys: variable "rules_map" { default = { http = { from_port = 80, to_port = 80, protocol = "tcp", cidr = "0.0.0.0/0" }, https = { from_port = 443, to_port = 443, protocol = "tcp", cidr = "10.0.0.0/16" } } } resource "aws_security_group" "example" { name = "example-sg" dynamic "ingress" { for_each = var.rules_map content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } } } This keeps keys stable and avoids unnecessary changes.
Result
Terraform applies only the necessary changes, preserving existing rules when possible.
Understanding how Terraform tracks dynamic blocks prevents costly downtime and resource churn in production.
Under the Hood
Terraform processes dynamic blocks by iterating over the for_each collection during the plan phase. For each item, it generates a nested block with attributes filled from the current item. Internally, Terraform assigns keys to each nested block to track them across applies. These keys determine if a block is new, changed, or removed. The dynamic block itself is a meta-construct that expands into multiple static blocks before resource creation.
Why designed this way?
Dynamic blocks were introduced to reduce repetitive code and improve maintainability. Before them, users had to copy-paste many similar blocks, increasing errors and making updates tedious. The design balances flexibility and simplicity by allowing loops only inside resources, avoiding complex language features that could confuse beginners.
Terraform Resource
┌─────────────────────────────┐
│ aws_security_group           │
│ ├─ static ingress block      │
│ ├─ dynamic ingress block     │
│ │   ├─ for_each collection   │
│ │   ├─ generate nested blocks│
│ │   └─ assign keys for state │
│ └─ other attributes          │
└─────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do dynamic blocks create separate resources or nested blocks inside one resource? Commit to your answer.
Common Belief:Dynamic blocks create multiple separate security group resources automatically.
Tap to reveal reality
Reality:Dynamic blocks generate multiple nested blocks inside a single resource, not multiple resources.
Why it matters:Misunderstanding this leads to wrong Terraform code structure and unexpected resource counts, causing deployment failures.
Quick: Can you use dynamic blocks anywhere in Terraform, like at the top level? Commit to your answer.
Common Belief:Dynamic blocks can be used anywhere in Terraform code, including top-level resource definitions.
Tap to reveal reality
Reality:Dynamic blocks only work inside resource or module blocks to generate nested blocks, not at the top level.
Why it matters:Trying to use dynamic blocks incorrectly causes syntax errors and confusion about Terraform's capabilities.
Quick: Does changing the order of items in a dynamic block's for_each always keep existing rules intact? Commit to your answer.
Common Belief:Changing the order of items in the for_each list does not affect existing security group rules.
Tap to reveal reality
Reality:Changing order in a list for for_each changes keys, causing Terraform to destroy and recreate rules unnecessarily.
Why it matters:This can cause downtime or security gaps due to unintended resource replacements.
Quick: Can you use dynamic blocks to create rules with different attribute sets in one block? Commit to your answer.
Common Belief:Dynamic blocks require all repeated blocks to have identical attributes and cannot handle variations.
Tap to reveal reality
Reality:Dynamic blocks can generate blocks with different attributes by using maps or objects with varied values.
Why it matters:Knowing this allows flexible and powerful rule definitions, avoiding rigid and duplicated code.
Expert Zone
1
Using maps with stable keys in for_each prevents unnecessary resource replacements caused by list index shifts.
2
Dynamic blocks do not support complex conditionals inside content blocks; filtering must happen before for_each.
3
Terraform state tracks nested blocks by keys, so changing keys or removing them can orphan resources if not handled carefully.
When NOT to use
Avoid dynamic blocks when rules are very few and static, as they add complexity. For highly dynamic or conditional rules, consider using Terraform modules or external scripts to generate configurations. Also, if you need to manage rules across multiple security groups with complex dependencies, dynamic blocks alone may not suffice.
Production Patterns
In production, teams use dynamic blocks with input variables to manage security groups for multiple environments. They combine dynamic blocks with maps keyed by environment or role names to keep rules organized and stable. They also use conditionals to enable or disable rules per environment, ensuring secure and flexible access control.
Connections
Terraform Modules
Builds-on
Understanding dynamic blocks helps when creating reusable modules that generate security groups with variable rules based on input parameters.
Firewall Rule Management
Same pattern
Dynamic blocks in Terraform mirror how firewall rule sets are managed in network devices by templating repeated rules, showing a common pattern in infrastructure automation.
Mail Merge in Document Editing
Analogy-based
Just like mail merge automates sending personalized letters to many recipients, dynamic blocks automate creating many similar infrastructure rules from a template.
Common Pitfalls
#1Using a list without stable keys for for_each causes resource churn.
Wrong approach:dynamic "ingress" { for_each = var.rules_list content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } }
Correct approach:dynamic "ingress" { for_each = var.rules_map content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } }
Root cause:Lists use numeric indexes as keys, which change when order or items change, causing Terraform to replace resources.
#2Trying to use dynamic blocks at the top level of Terraform code causes errors.
Wrong approach:dynamic "ingress" { for_each = var.rules content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } }
Correct approach:resource "aws_security_group" "example" { name = "example-sg" dynamic "ingress" { for_each = var.rules content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } } }
Root cause:Dynamic blocks must be inside resource or module blocks; they cannot stand alone.
#3Not filtering rules before for_each leads to unwanted rules being created.
Wrong approach:dynamic "ingress" { for_each = var.rules content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } }
Correct approach:dynamic "ingress" { for_each = [for r in var.rules : r if r.enabled] content { from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = [each.value.cidr] } }
Root cause:Terraform does not support conditionals inside content blocks; filtering must happen in for_each.
Key Takeaways
Dynamic blocks automate creating multiple similar nested blocks inside a Terraform resource, reducing repetitive code.
They work by looping over a collection and generating one nested block per item, filling attributes dynamically.
Using maps with stable keys for for_each prevents unnecessary resource replacements caused by list index changes.
Dynamic blocks must be inside resource or module blocks and cannot be used at the top level.
Filtering collections before for_each is essential to conditionally create nested blocks, as conditionals inside content are not supported.