TFLAB12-dynamic Blocks: Generating Repeated Nested Blocks

dynamic Blocks: Generating Repeated Nested Blocks

🔧 Terraform Core ⭐ Intermediate dynamic content {} iterator Nested Blocks

Scenario

You are managing a security group that needs 8 different ingress rules — one for SSH, HTTP, HTTPS, a monitoring port, and four application ports. Writing 8 separate ingress {} blocks is verbose, error-prone, and hard to maintain. When a new port needs to be added, you want to just add it to a variable list, not write another 6-line block.

dynamic blocks let you generate repeated nested blocks (like ingress) from a list or map — DRY and data-driven.

Your Objectives
  • Define a list(object(...)) variable for ingress rules.
  • Use a dynamic "ingress" block to generate one ingress rule per list entry.
  • Access attributes inside the dynamic block using ingress.value.port (or a custom iterator name).
  • Add a custom iterator name to the dynamic block.
  • Output the security group ID and the number of rules created.

Additional Context

dynamic blocks are syntactic sugar for generating repeated nested blocks. They replace patterns like "write 8 ingress blocks" with "loop over a list and generate ingress blocks dynamically."

The content {} block inside a dynamic is where you define the actual attributes of the nested block. By default, the iterator name matches the label (e.g., dynamic "ingress"ingress.value). You can set a custom iterator name for clarity.

HashiCorp recommends using dynamic blocks sparingly — they reduce readability when overused. Use them when you have a genuinely variable number of nested blocks driven by input data.

variables.tf

variable "aws_region" { type = string; default = "us-east-1" }

# Each object defines one ingress rule
variable "ingress_rules" {
  description = "List of ingress rules for the security group."
  type = list(object({
    port        = number
    protocol    = string
    cidr_blocks = list(string)
    description = string
  }))
  default = [
    { port = 22,   protocol = "tcp", cidr_blocks = ["10.0.0.0/8"],   description = "SSH from internal" },
    { port = 80,   protocol = "tcp", cidr_blocks = ["0.0.0.0/0"],    description = "HTTP from anywhere" },
    { port = 443,  protocol = "tcp", cidr_blocks = ["0.0.0.0/0"],    description = "HTTPS from anywhere" },
    { port = 8080, protocol = "tcp", cidr_blocks = ["10.0.0.0/8"],   description = "App port from internal" },
    { port = 9090, protocol = "tcp", cidr_blocks = ["10.0.0.0/8"],   description = "Monitoring from internal" },
  ]
}

main.tf

# main.tf

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

provider "aws" { region = var.aws_region }

resource "aws_security_group" "app" {
  name        = "app-sg-dynamic"
  description = "Security group with dynamic ingress rules"

  # ─── DYNAMIC BLOCK: generates one ingress {} per list entry ───
  dynamic "ingress" {
    for_each = var.ingress_rules       # iterate over the list
    iterator = rule                     # custom iterator name (optional)

    content {
      from_port   = rule.value.port     # rule.value = current list element
      to_port     = rule.value.port
      protocol    = rule.value.protocol
      cidr_blocks = rule.value.cidr_blocks
      description = rule.value.description
    }
  }

  # Static egress — allow all outbound
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    description = "All outbound"
  }

  tags = { Name = "app-sg", ManagedBy = "terraform" }
}

outputs.tf

output "security_group_id" { value = aws_security_group.app.id }
output "ingress_rule_count" {
  description = "Number of ingress rules generated."
  value       = length(var.ingress_rules)
}

Workflow Commands

terraform init && terraform apply

# Add a new rule — just add to the list variable, no code change needed
terraform apply -var='ingress_rules=[{"port":22,"protocol":"tcp","cidr_blocks":["10.0.0.0/8"],"description":"SSH"},{"port":80,"protocol":"tcp","cidr_blocks":["0.0.0.0/0"],"description":"HTTP"},{"port":3000,"protocol":"tcp","cidr_blocks":["10.0.0.0/8"],"description":"New dev port"}]'

terraform destroy
✓ Expected Output After Apply
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

ingress_rule_count = 5
security_group_id  = "sg-0abc1234def56789"