Terraform – Dynamic Block

A Dynamic block is similar to for expression but creates nested blocks instead of a complex typed value. It iterates over a given complex value and generates a nested block for each element of that complex value. Unlike the count block, which will iterate over the resource, the dynamic will reside inside the resource block.

Dynamic blocks are supported inside the following blocks,

  • Resource
  • Data
  • Provider
  • Provisioner

Dynamic blocks can be used for resources like Setting, Security Group, etc.

Let us consider the example of creating a security group with multiple ingress rules for ports (80, 443, 3306, and 22). In an ideal scenario, we will end up copy-pasting the ingress rule for each port in the security group resource block.

Security Group (Without Dynamic Block)

// Security Group without Dynamic Block
resource "aws_security_group" "lamp_securitygroup_basic" {
  name        = "LAMP Security Group Basic"
  description = "LAMP Server Security Group Basic ${terraform.workspace}" // call the workspace 
  vpc_id      = data.aws_vpc.get_vpc_id.id
  tags        = local.common_tags
  // in-bound rules
  ingress {
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
  ingress {
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
  ingress {
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
  ingress {
    from_port        = 3306
    to_port          = 3306
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
  // outbound rules
  egress {
    from_port        = 0
    to_port          = 0
    protocol         = -1
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}

The above code will work perfectly fine, but as your security group rules go on increasing, you will need to copy-paste the ingress block for each rule, which will make the code hard to maintain and update.

To overcome this issue, Terraform provides us with a dynamic block. We can write the same code using dynamic block,

Security Group (Dynamic Block)

// Data block for VPC ID
data "aws_vpc" "get_vpc_id" {
  default = true
}
// variable declaration 
variable "port" {
  description = "Enter the ports to be configured in SG Ingress rule"
  default     = [80, 22, 443, 3306]
}
// Create Security Group uisng Dynamic Block
resource "aws_security_group" "lamp_sg" {
  name        = "LAMP Security Group"
  description = "LAMP Server Security Group ${terraform.workspace}" // call the workspace 
  vpc_id      = data.aws_vpc.get_vpc_id.id
  tags        = local.common_tags
  dynamic "ingress" {
    for_each = var.port
    content {
      description      = "Security rule for inbound ${ingress.value}"
      from_port        = ingress.value // The block should use block name to fetch the value and key
      to_port          = ingress.value
      protocol         = "tcp"
      cidr_blocks      = ["0.0.0.0/0"]
      ipv6_cidr_blocks = ["::/0"]
    }
  }
  // outbound rules
  egress {
    from_port        = 0
    to_port          = 0
    protocol         = -1
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}

In our example, we are producing a nested block of ingress rules,

  • The label of our dynamic block is “ingress”, you can change the label, as you per the requirement, like if you want to generate “setting” nested blocks we can name the dynamic block as “setting”.
  • For_each – Argument will provide the complex argument to iterate over.
  • Content Block – defines the body of each generated block. In our example it will be the ingress block, iterating over multiple ports.

In multi-level nested block, we can have multi-level nested dynamic blocks, ie the nested dynamic bock within the dynamic block.