Terraform Dynamic Blocks

Introduction
In this part, we’ll explore some advanced features of Terraform. You’ll learn about variable types, constructs like for_each, and how to use dynamic blocks to make your Terraform code cleaner and less verbose.
Introduction to Variables
Just like other programming languages, Terraform’s HCL (HashiCorp Configuration Language) supports variables and data types.
Modules and resource blocks often accept arbitrary user inputs. The best practice is to define variables in a terraform.tfvars file, which supplies values to Terraform during runtime.
Let’s start with a simple example.
Example Usage
We’ll create a Terraform project that provisions an S3 bucket. This project will include the following files:
.
├── backend.tf
├── main.tf
├── terraform.tfvars
└── variables.tf
main.tf
This file defines the S3 bucket and uses variables via the syntax var.variable_name.
provider "aws" {
profile = var.profile
region = var.region
}
resource "aws_s3_bucket" "testbucket" {
bucket = var.bucket_name
}
variables.tf
Here we declare the variables used in the project:
variable "region" {
type = string
default = "us-east-1"
}
variable "profile" {
type = string
default = "profile-name-to-use"
}
variable "bucket_name" {
type = string
description = "Name of S3 bucket to create"
}
You can also mark variables as sensitive or add validation rules for stricter control.
terraform.tfvars
This file assigns values to the declared variables:
bucket_name = "test_bucket"
Terraform Commands
Here’s a typical Terraform workflow:
terraform init # Initialize backend and providers
terraform fmt # Format code
terraform validate # Validate for syntax errors
terraform plan # Preview changes
terraform apply # Apply changes
terraform destroy # Destroy resources
Types of Variables
Terraform supports two broad categories of variables:
1. Primitives:string, boolean, number
2. Complex:set, map, object, tuple, list, any
Here’s an example of a complex variable definition:
variable "igress_params" {
type = map(object({
port = number
proto = string
cidr = list(string)
}))
default = {
"rule1" = {
port = 22
proto = "tcp"
cidr = ["0.0.0.0/0"]
},
"rule2" = {
port = 80
proto = "tcp"
cidr = ["1.2.3.4/32"]
}
}
}
This map(object) variable defines ingress rules that you can later reference while creating security groups or VPCs.
Introduction to for_each
The for_each construct is useful when you want to create multiple resources of the same type with varying values. For example, creating three S3 buckets for dev, uat, and prod.
provider "aws" {
profile = "aws-profile-to-use"
region = "us-east-1"
}
variable "s3_buckets" {
type = set(string)
description = "Name of S3 buckets to create"
default = ["test-dev", "test-uat", "test-prod"]
}
resource "aws_s3_bucket" "testbucket" {
for_each = var.s3_buckets
bucket = each.value
}
Terraform will iterate through s3_buckets and create three separate S3 buckets.
The dynamic Keyword
When defining resources like security groups, you might need multiple ingress or egress rules. Manually declaring them can get verbose and hard to maintain.
Without Dynamic Blocks
resource "aws_security_group" "my-sg" {
vpc_id = aws_vpc.my-vpc.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
This approach quickly becomes messy for many rules.
With Dynamic Blocks
We can make this clean and reusable using the dynamic keyword.
variable "igress_params" {
type = map(object({
port = number
proto = string
cidr = list(string)
}))
default = {
"rule1" = {
port = 22
proto = "tcp"
cidr = ["0.0.0.0/0"]
},
"rule2" = {
port = 80
proto = "tcp"
cidr = ["1.2.3.4/32"]
}
}
}
resource "aws_vpc" "my-vpc" {
cidr_block = "10.0.0.0/16"
}
resource "aws_security_group" "my-sg" {
vpc_id = aws_vpc.my-vpc.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
dynamic "ingress" {
for_each = var.igress_params
content {
from_port = ingress.value["port"]
to_port = ingress.value["port"]
protocol = ingress.value["proto"]
cidr_blocks = ingress.value["cidr"]
}
}
}
Terraform will iterate through each map entry in igress_params and dynamically create multiple ingress blocks — one for each rule.
Key benefit: Your Terraform files stay concise, and you can easily scale your configuration by just editing variable definitions.
In Summary
Dynamic blocks and constructs like for_each bring programmability and flexibility into Terraform — reducing code repetition while improving readability and scalability.


