Loops and Conditionals: Dynamic Configurations with Count & For_Each
Your complete guide to creating flexible, DRY Terraform configurations that adapt to your infrastructure needs—without repeating yourself.
📅 Published: Feb 2026
⏱️ Estimated Reading Time: 24 minutes
🏷️ Tags: Terraform Loops, Conditionals, Count, For_Each, Dynamic Blocks, Meta-Arguments
🎭 Introduction: The Problem of Repetition
The Copy-Paste Trap
Every Terraform user eventually faces this moment. You need three subnets. So you write:
resource "aws_subnet" "public_1" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = "us-west-2a" map_public_ip_on_launch = true } resource "aws_subnet" "public_2" { vpc_id = aws_vpc.main.id cidr_block = "10.0.2.0/24" availability_zone = "us-west-2b" map_public_ip_on_launch = true } resource "aws_subnet" "public_3" { vpc_id = aws_vpc.main.id cidr_block = "10.0.3.0/24" availability_zone = "us-west-2c" map_public_ip_on_launch = true }
This works. It's also a maintenance nightmare.
❌ Need to add a fourth subnet? Copy-paste-modify.
❌ Need to change the CIDR block pattern? Edit three places.
❌ Need to disable public IPs? Edit three places.
❌ Someone accidentally uses us-west-2a twice? Good luck catching that.
This is the copy-paste trap, and it's where most Terraform users start. Loops are how you escape.
What Loops Solve
Terraform provides two meta-arguments for creating multiple resources from a single definition:
| Meta-argument | Works With | Best For |
|---|---|---|
count | Resources, modules, data sources | Simple numbered lists |
for_each | Resources, modules, data sources | Maps and sets of strings |
These transform repetitive configurations into dynamic, data-driven ones:
# Instead of 3 nearly-identical subnet blocks... resource "aws_subnet" "public" { count = 3 vpc_id = aws_vpc.main.id cidr_block = "10.0.${count.index + 1}.0/24" availability_zone = data.aws_availability_zones.available.names[count.index] map_public_ip_on_launch = true }
One block. Three resources. Zero repetition.
🔢 Count: The Simple Loop
How Count Works
count is a meta-argument that accepts a non-negative integer. When you add it to a resource, module, or data source, Terraform creates that many instances.
resource "aws_instance" "web" { count = 3 ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro" tags = { Name = "web-server-${count.index}" } }
This creates three EC2 instances, accessible as:
aws_instance.web[0]aws_instance.web[1]aws_instance.web[2]
The count.index special variable starts at 0 and increments with each instance. This is your primary tool for making each instance different.
Count with Lists
The most common pattern: using count with length() to iterate over a list.
variable "subnet_cidrs" { description = "CIDR blocks for public subnets" type = list(string) default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] } resource "aws_subnet" "public" { count = length(var.subnet_cidrs) vpc_id = aws_vpc.main.id cidr_block = var.subnet_cidrs[count.index] availability_zone = data.aws_availability_zones.available.names[count.index] tags = { Name = "public-subnet-${count.index + 1}" } }
Benefits:
Add subnets by adding entries to
var.subnet_cidrsRemove subnets by removing entries
Never touch the resource block again
Count with Conditional Logic
Count can also be used for conditional resource creation—the most common pattern in Terraform.
variable "create_bastion" { description = "Whether to create a bastion host" type = bool default = false } resource "aws_instance" "bastion" { # Create 1 instance if true, 0 if false count = var.create_bastion ? 1 : 0 ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro" tags = { Name = "bastion-host" } }
Then, to reference this conditionally created resource:
# Safe way to access conditional resource output "bastion_ip" { value = var.create_bastion ? aws_instance.bastion[0].public_ip : null } # Or use try() to handle missing resources output "bastion_ip_alt" { value = try(aws_instance.bastion[0].public_ip, null) }
This pattern is used for:
Environment-specific resources (dev vs. prod)
Feature flags (enable_monitoring, enable_backups)
Optional components (bastion hosts, NAT gateways)
One-time setup resources (bootstrap jobs)
Count Limitations
⚠️ Count has a critical limitation: it works by position, not by value.
variable "users" { type = list(string) default = ["alice", "bob", "charlie"] } resource "aws_iam_user" "this" { count = length(var.users) name = var.users[count.index] }
What happens when you remove "bob" from the list?
variable "users" { default = ["alice", "charlie"] # Bob removed }
Terraform sees:
Index 0: "alice" → matches existing user "alice" → update
Index 1: "charlie" → WAS "bob", NOW "charlie" → destroy bob, create charlie 😱
This is called index instability. When the list changes, Terraform renumbers the instances. Resources can be unexpectedly destroyed and recreated.
Solution: Use for_each when the order might change or when stable identities are important.
🔁 For_Each: The Stable Loop
How For_Each Works
for_each is a meta-argument that accepts a map or set of strings. It creates one instance per item, keyed by the map key or set value.
variable "users" { description = "Map of IAM users to create" type = map(object({ groups = list(string) path = optional(string, "/") })) default = { "alice" = { groups = ["developers", "ops"] } "bob" = { groups = ["developers"] } "charlie" = { groups = ["managers"] path = "/leadership/" } } } resource "aws_iam_user" "this" { for_each = var.users name = each.key path = each.value.path tags = { Groups = join(",", each.value.groups) } }
The magic of for_each: Each instance is identified by its key, not its position. Remove "bob" from the map, and Terraform knows exactly which user to delete. No renumbering, no accidental recreation.
Special variables:
each.key— The map key (or set value)each.value— The map value (for maps; for sets, same as key)
For_Each with Sets
When you don't need complex values, a set of strings is perfect:
variable "allowed_accounts" { description = "AWS account IDs allowed to assume this role" type = set(string) default = ["123456789012", "210987654321"] } resource "aws_iam_role" "this" { name = "cross-account-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { AWS = var.allowed_accounts # This works directly! } Action = "sts:AssumeRole" } ] }) } # But for resources that need per-item configuration: resource "aws_iam_role_policy_attachment" "this" { for_each = var.allowed_accounts role = aws_iam_role.this.name policy_arn = "arn:aws:iam::${each.value}:policy/OrganizationAccountAccessRole" }
For_Each with Maps of Objects
This is the most powerful pattern—full configuration for each instance:
variable "databases" { description = "Database configurations" type = map(object({ engine = string engine_version = string instance_class = string storage_gb = number multi_az = optional(bool, false) backup_days = optional(number, 7) })) default = { "users-db" = { engine = "postgres" engine_version = "14.7" instance_class = "db.t3.micro" storage_gb = 100 multi_az = false } "products-db" = { engine = "mysql" engine_version = "8.0" instance_class = "db.t3.small" storage_gb = 200 multi_az = true backup_days = 30 } "analytics-db" = { engine = "aurora-postgresql" engine_version = "13.6" instance_class = "db.r5.large" storage_gb = 1000 backup_days = 14 } } } resource "aws_db_instance" "this" { for_each = var.databases identifier = each.key engine = each.value.engine engine_version = each.value.engine_version instance_class = each.value.instance_class allocated_storage = each.value.storage_gb multi_az = each.value.multi_az backup_retention_period = each.value.backup_days # ... other required arguments }
This is infrastructure as configuration. The module user provides a declarative map of what they want; the module creates exactly that.
For_Each on Modules
One of the most powerful features in Terraform—iterating over entire modules:
# modules/team-environment/main.tf variable "team_name" { type = string } variable "environment_config" { type = object({ instance_type = string instance_count = number enable_monitoring = bool }) } resource "aws_instance" "app" { count = var.environment_config.instance_count instance_type = var.environment_config.instance_type # ... other configuration } # root/main.tf locals { teams = { "payments" = { instance_type = "t3.large" instance_count = 3 enable_monitoring = true } "checkout" = { instance_type = "t3.medium" instance_count = 2 enable_monitoring = false } "fraud" = { instance_type = "t3.xlarge" instance_count = 5 enable_monitoring = true } } } module "team_environments" { source = "./modules/team-environment" for_each = local.teams team_name = each.key environment_config = each.value } output "all_instances" { value = { for team, module in module.team_environments : team => module.instance_ids } }
This is how platform teams scale. One module definition, dozens of instantiations, hundreds of resources—all managed with clean, declarative configuration.
🆚 Count vs. For_Each: When to Use Which
Decision Matrix
| Scenario | Use Count | Use For_Each |
|---|---|---|
| Simple numbered list (0,1,2,3) | ✅ Perfect | 🤔 Possible but awkward |
| Stable order guaranteed | ✅ Good | ✅ Also good |
| Order may change | ❌ Danger! | ✅ Safe |
| Resources may be added/removed | ⚠️ Risky | ✅ Safe |
| Each instance needs unique config | ⚠️ Limited to count.index | ✅ Full configuration per key |
| Conditional resource (create if true) | ✅ count = condition ? 1 : 0 | ⚠️ for_each = condition ? {name = {}} : {} |
| Zero instances needed | ✅ count = 0 | ✅ for_each = {} |
Rule of Thumb
Use count when:
You need a simple numeric range (0..N)
You're conditionally creating a single resource
The order is stable and you never remove items from the middle
Use for_each when:
You have a set of strings or a map
Resources may be added or removed
Each instance needs different configuration
You want stable, predictable resource addresses
When in doubt, choose for_each. It's more explicit, more stable, and handles changes gracefully.
🧩 Dynamic Blocks: Nested Configuration Loops
What Are Dynamic Blocks?
Dynamic blocks allow you to create multiple nested blocks within a resource. They're like for_each for arguments that are blocks, not entire resources.
resource "aws_security_group" "web" { name = "web-sg" vpc_id = aws_vpc.main.id # Dynamic block for ingress rules dynamic "ingress" { for_each = var.ingress_rules content { description = ingress.value.description from_port = ingress.value.port to_port = ingress.value.port protocol = ingress.value.protocol cidr_blocks = ingress.value.cidr_blocks } } }
Without dynamic blocks, you'd need separate security group rule resources. With dynamic blocks, everything is contained within the security group definition.
Dynamic Block Syntax
dynamic "block_type" { for_each = COLLECTION # Map or set to iterate over # Iterator variables (optional) iterator = NAME # Defaults to block_type content { # Can reference: # - block_type.value (or iterator.value) # - block_type.key (map key, or index for list) } }
Example with explicit iterator:
dynamic "tag" { for_each = var.tags iterator = tag_item # Instead of default "tag" content { key = tag_item.key value = tag_item.value } }
Real-World Dynamic Block Examples
1. Security group rules
variable "ingress_rules" { type = list(object({ description = string port = number protocol = string cidr_blocks = list(string) })) default = [ { description = "HTTP" port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }, { description = "HTTPS" port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ] } resource "aws_security_group" "web" { name = "web-sg" vpc_id = aws_vpc.main.id dynamic "ingress" { for_each = var.ingress_rules content { description = ingress.value.description from_port = ingress.value.port to_port = ingress.value.port protocol = ingress.value.protocol cidr_blocks = ingress.value.cidr_blocks } } }
2. IAM policy statements
variable "s3_permissions" { type = map(object({ actions = list(string) resources = list(string) effect = optional(string, "Allow") })) } resource "aws_iam_policy" "s3_access" { name = "s3-access-policy" policy = jsonencode({ Version = "2012-10-17" Statement = [ for name, config in var.s3_permissions : { Sid = name Effect = config.effect Action = config.actions Resource = config.resources } ] }) }
3. EBS block devices
variable "additional_volumes" { type = list(object({ device_name = string volume_size = number volume_type = string encrypted = optional(bool, true) })) default = [] } resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = "t2.micro" dynamic "ebs_block_device" { for_each = var.additional_volumes content { device_name = ebs_block_device.value.device_name volume_size = ebs_block_device.value.volume_size volume_type = ebs_block_device.value.volume_type encrypted = ebs_block_device.value.encrypted } } }
4. CloudWatch alarm dimensions
variable "alarm_dimensions" { type = map(string) default = { Environment = "production" Service = "web" } } resource "aws_cloudwatch_metric_alarm" "cpu_high" { alarm_name = "cpu-utilization-high" comparison_operator = "GreaterThanThreshold" evaluation_periods = 2 metric_name = "CPUUtilization" namespace = "AWS/EC2" period = 60 statistic = "Average" threshold = 80 dynamic "dimension" { for_each = var.alarm_dimensions content { name = dimension.key value = dimension.value } } }
Dynamic Block Limitations
⚠️ Dynamic blocks cannot be used everywhere. They only work in locations where Terraform expects a specific block type, not for arbitrary nesting.
❌ Cannot use dynamic blocks for:
Resource-level meta-arguments (
count,for_each,provider)Module blocks
Provisioner blocks
Nested dynamic blocks (no nesting)
✅ Can use dynamic blocks for:
ingress/egressinaws_security_groupdimensioninaws_cloudwatch_metric_alarmtagin most AWS resourceslogginginaws_s3_bucketlifecycle_ruleinaws_s3_bucketAny block that repeats within a single resource
🔄 For Expressions: Loops in Local Values
What Are For Expressions?
For expressions create new collections from existing collections. They're used in locals and variable definitions, not directly in resources.
locals { users = ["alice", "bob", "charlie"] # Transform list → list uppercase_users = [for name in local.users : upper(name)] # Result: ["ALICE", "BOB", "CHARLIE"] # Transform list → map user_map = { for name in local.users : name => upper(name) } # Result: { alice = "ALICE", bob = "BOB", charlie = "CHARLIE" } # Transform with index users_with_index = [for i, name in local.users : "${i + 1}: ${name}"] # Result: ["1: alice", "2: bob", "3: charlie"] }
Filtering with For Expressions
For expressions can include if clauses to filter collections:
locals { instances = [ { name = "web-1", type = "t3.micro", environment = "dev" }, { name = "web-2", type = "t3.small", environment = "prod" }, { name = "db-1", type = "t3.medium", environment = "prod" }, { name = "web-3", type = "t3.micro", environment = "dev" }, ] # Filter: only prod instances prod_instances = [ for instance in local.instances : instance if instance.environment == "prod" ] # Result: [web-2, db-1] # Filter and transform prod_instance_names = [ for instance in local.instances : instance.name if instance.environment == "prod" ] # Result: ["web-2", "db-1"] # Filter into map prod_instance_map = { for instance in local.instances : instance.name => instance.type if instance.environment == "prod" } # Result: { "web-2" = "t3.small", "db-1" = "t3.medium" } }
Advanced For Expression Patterns
1. Flattening nested structures:
locals { teams = { "platform" = ["alice", "bob"] "data" = ["charlie", "diana"] "product" = ["eve", "frank"] } # Flatten to list of all users all_users = flatten([ for team, members in local.teams : [ for member in members : { team = team member = member } ] ]) # Result: [{team="platform",member="alice"}, {team="platform",member="bob"}, ...] }
2. Grouping resources:
locals { subnets = [ { az = "us-west-2a", type = "public", cidr = "10.0.1.0/24" }, { az = "us-west-2b", type = "public", cidr = "10.0.2.0/24" }, { az = "us-west-2a", type = "private", cidr = "10.0.10.0/24" }, { az = "us-west-2b", type = "private", cidr = "10.0.20.0/24" }, ] # Group by availability zone subnets_by_az = { for subnet in local.subnets : subnet.az => subnet... } # Result: # us-west-2a = [public, private] # us-west-2b = [public, private] # Group by type subnets_by_type = { for subnet in local.subnets : subnet.type => subnet... } # Result: # public = [az-a, az-b] # private = [az-a, az-b] }
3. Creating lookup maps:
locals { # Create a map from subnet CIDR to subnet object subnet_lookup = { for subnet in aws_subnet.public : subnet.cidr_block => { id = subnet.id az = subnet.availability_zone } } } # Then use it elsewhere resource "aws_instance" "web" { subnet_id = local.subnet_lookup["10.0.1.0/24"].id # ... }
🎯 Conditional Patterns Beyond Count
Conditional with For_Each
While count is simpler for single-resource conditions, for_each can also do conditionals:
# Using count (simpler) resource "aws_instance" "bastion" { count = var.create_bastion ? 1 : 0 # ... } # Using for_each (more consistent if you're already using for_each) resource "aws_instance" "bastion" { for_each = var.create_bastion ? { default = {} } : {} # ... (each.value is empty object) }
When to use which:
Count: When you only need conditional creation
For_each: When you need conditional creation AND you're already using
for_eachelsewhere for consistency
Conditional Expressions in Resources
You can use conditional logic inside resource arguments without loops:
resource "aws_db_instance" "main" { engine = "postgres" engine_version = "14.7" instance_class = var.environment == "prod" ? "db.r5.large" : "db.t3.micro" multi_az = var.environment == "prod" ? true : false # Conditionally set backup retention backup_retention_period = var.environment == "prod" ? 30 : 7 # Conditionally enable deletion protection deletion_protection = var.environment == "prod" # Conditionally set parameter group parameter_group_name = var.environment == "prod" ? aws_db_parameter_group.prod.name : aws_db_parameter_group.dev.name }
Conditional with Lookup Maps
For complex conditional logic, a lookup map is often clearer than nested ternaries:
locals { instance_types = { dev = "t2.micro" staging = "t3.small" prod = "t3.large" } instance_counts = { dev = 1 staging = 2 prod = 3 } enable_monitoring = { dev = false staging = true prod = true } } resource "aws_instance" "web" { count = local.instance_counts[var.environment] instance_type = local.instance_types[var.environment] monitoring = local.enable_monitoring[var.environment] }
This is much more maintainable than:
instance_type = var.environment == "dev" ? "t2.micro" : var.environment == "staging" ? "t3.small" : "t3.large"
🧪 Practice Exercises
Exercise 1: Convert Count to For_Each
Task: Convert this configuration from count to for_each to prevent index instability.
variable "buckets" { description = "List of S3 bucket names" type = list(string) default = ["logs", "backups", "data"] } resource "aws_s3_bucket" "this" { count = length(var.buckets) bucket = var.buckets[count.index] tags = { Name = var.buckets[count.index] } }
Solution:
variable "buckets" { description = "List of S3 bucket names" type = list(string) default = ["logs", "backups", "data"] } # Convert list to set for for_each resource "aws_s3_bucket" "this" { for_each = toset(var.buckets) bucket = each.key tags = { Name = each.key } }
Now when you remove "backups" from the list, Terraform knows exactly which bucket to delete—no accidental renumbering!
Exercise 2: Complex Dynamic Block
Task: Create a security group module that accepts a flexible list of ingress rules, including rules that reference other security groups.
variable "security_group_rules" { description = "Ingress rules for security group" type = list(object({ description = string from_port = number to_port = number protocol = string cidr_blocks = optional(list(string), []) security_groups = optional(list(string), []) })) } # Your solution here
Solution:
variable "security_group_rules" { description = "Ingress rules for security group" type = list(object({ description = string from_port = number to_port = number protocol = string cidr_blocks = optional(list(string), []) security_groups = optional(list(string), []) })) } resource "aws_security_group" "this" { name = var.name vpc_id = var.vpc_id dynamic "ingress" { for_each = var.security_group_rules content { description = ingress.value.description from_port = ingress.value.from_port to_port = ingress.value.to_port protocol = ingress.value.protocol cidr_blocks = ingress.value.cidr_blocks # Only include security_groups if provided security_groups = length(ingress.value.security_groups) > 0 ? ingress.value.security_groups : null } } }
Exercise 3: Multi-Environment Configuration
Task: Create a reusable EKS cluster module that can be instantiated multiple times with different configurations per team/environment.
# Solution: # modules/eks-cluster/variables.tf variable "cluster_config" { description = "EKS cluster configuration" type = object({ name = string environment = string kubernetes_version = string node_groups = map(object({ instance_types = list(string) min_size = number max_size = number desired_size = number })) }) } # modules/eks-cluster/main.tf resource "aws_eks_cluster" "this" { name = "${var.cluster_config.name}-${var.cluster_config.environment}" role_arn = aws_iam_role.cluster.arn version = var.cluster_config.kubernetes_version vpc_config { subnet_ids = var.subnet_ids } } resource "aws_eks_node_group" "this" { for_each = var.cluster_config.node_groups cluster_name = aws_eks_cluster.this.name node_group_name = each.key node_role_arn = aws_iam_role.node_group.arn subnet_ids = var.subnet_ids instance_types = each.value.instance_types scaling_config { min_size = each.value.min_size max_size = each.value.max_size desired_size = each.value.desired_size } depends_on = [ aws_eks_cluster.this, aws_iam_role_policy_attachment.node_group ] } # root/main.tf locals { clusters = { "platform-dev" = { environment = "dev" kubernetes_version = "1.28" node_groups = { "general" = { instance_types = ["t3.medium"] min_size = 1 max_size = 3 desired_size = 2 } "cpu-heavy" = { instance_types = ["c5.large"] min_size = 0 max_size = 5 desired_size = 0 } } } "platform-prod" = { environment = "prod" kubernetes_version = "1.28" node_groups = { "general" = { instance_types = ["t3.large", "t3.xlarge"] min_size = 3 max_size = 10 desired_size = 5 } "cpu-heavy" = { instance_types = ["c5.xlarge"] min_size = 1 max_size = 20 desired_size = 2 } "memory-heavy" = { instance_types = ["r5.large"] min_size = 1 max_size = 10 desired_size = 2 } } } } } module "eks_clusters" { source = "./modules/eks-cluster" for_each = local.clusters cluster_config = { name = each.key environment = each.value.environment kubernetes_version = each.value.kubernetes_version node_groups = each.value.node_groups } subnet_ids = module.vpc.private_subnet_ids }
📋 Loop and Conditional Patterns Cheat Sheet
| Pattern | Code | Use Case |
|---|---|---|
| Count loop | count = 5 | Simple numbered resources |
| Count conditional | count = var.create ? 1 : 0 | Optional resource |
| Count with list | count = length(var.list) | Create per list item |
| For_each with set | for_each = toset(var.list) | Create per unique string |
| For_each with map | for_each = var.map | Create with configuration |
| For_each conditional | for_each = var.create ? {key={}} : {} | Optional with for_each |
| Dynamic block | dynamic "ingress" { for_each = var.rules } | Nested configuration blocks |
| For expression (list) | [for v in var.list : upper(v)] | Transform list → list |
| For expression (map) | {for k,v in var.map : k => upper(v)} | Transform map → map |
| For with filter | [for v in var.list : v if v != ""] | Filter collections |
| Flatten | flatten([[1,2], [3,4]]) | Flatten nested lists |
| Splat expression | var.list[*].id | Extract attribute from list of objects |
🎓 Summary: Dynamic Configurations Are Professional Configurations
The difference between junior and senior Terraform usage isn't knowing more resources—it's knowing how to make resources dynamic.
| Static Configuration | Dynamic Configuration | |
|---|---|---|
| Subnets | 3 nearly-identical blocks | count = 3 with CIDR calculation |
| Security rules | Copy-paste-modify | dynamic "ingress" with variable rules |
| Multiple environments | dev/, staging/, prod/ directories | One module, config maps per env |
| Multiple teams | Copy entire configurations | for_each on module calls |
| Adding new resource | Find where to paste | Add entry to configuration map |
Master these patterns and you'll:
Write 80% less code
Make 100x fewer copy-paste errors
Enable self-service infrastructure for your teams
Sleep better knowing your configurations are consistent
🔗 Master Terraform Loops with Hands-on Labs
Theory is essential, but loops are best learned by doing. Practice these patterns in real scenarios until they become muscle memory.
👉 Practice count, for_each, and dynamic blocks with guided exercises at:
https://devops.trainwithsky.com/
Our platform provides:
Real-time validation of your loop logic
Complex multi-resource challenges
Dynamic block configuration exercises
Module iteration with for_each
Performance optimization for large counts
Frequently Asked Questions
Q: Why can't I use count.index in for_each?
A: for_each uses map keys or set values as identifiers, not positions. If you need a numeric index, you probably want count. If you need both index and stable identity, use:
for_each = { for i, v in var.list : i => v }
Q: Can I use count and for_each together?
A: No. A resource or module block cannot have both count and for_each. Choose one.
Q: How many resources can I create with loops?
A: Terraform has no hard limit, but practically, keep each module under a few hundred resources. Beyond that, performance degrades and API rate limits become an issue.
Q: What's the performance impact of loops?
A: Each instance is a separate resource in Terraform's graph. Planning time scales roughly linearly with the number of resources. For very large counts (>1000), consider splitting into multiple configurations.
Q: Can I loop over a list of maps in a dynamic block?
A: Yes! The for_each in a dynamic block accepts any collection type. For a list of maps:
dynamic "block" { for_each = var.list_of_maps content { attr1 = block.value.key1 attr2 = block.value.key2 } }
Q: How do I conditionally include a dynamic block?
A: Filter the collection:
dynamic "ingress" { for_each = [ for rule in var.rules : rule if rule.enabled ] content { # ... } }
Q: Why does terraform plan show that my for_each resources will be destroyed and recreated when I change an unrelated value?
A: If you're using a computed value (like a resource attribute) as the for_each key, any change to that attribute will force recreation. Use static values (variables, locals, literals) as keys when possible.
Still confused about when to use count vs. for_each? Stuck on a dynamic block? Share your configuration in the comments—our community is here to help! 💬
Comments
Post a Comment