Terraform on Ubuntu 26.04 LTS - sudo-rs, APT Rollback, and Hardened Base Images
Install and run Terraform on Ubuntu 26.04 LTS Resolute Raccoon. Covers sudo-rs as default, APT 3.2 rollback, Kernel 7.0, Wayland-only, ROCm, and building...
DevOps
Learn Terraform dynamic blocks and for expressions. Generate repeated blocks from variables, filter lists, transform maps, and build DRY security groups
Dynamic blocks generate repeated nested blocks from a variable. Instead of writing the same block 10 times, you write it once with dynamic.
# Without dynamic — 5 ingress blocks, lots of repetition
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
# ... more rules
}variable "ingress_rules" {
default = [
{ port = 80, cidr = "0.0.0.0/0", description = "HTTP" },
{ port = 443, cidr = "0.0.0.0/0", description = "HTTPS" },
{ port = 22, cidr = "10.0.0.0/8", description = "SSH internal" },
{ port = 8080, cidr = "10.0.0.0/8", description = "App internal" },
{ port = 3306, cidr = "10.0.1.0/24", description = "MySQL" },
]
}
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = [ingress.value.cidr]
description = ingress.value.description
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}dynamic "BLOCK_NAME" {
for_each = COLLECTION
iterator = OPTIONAL_NAME # defaults to BLOCK_NAME
content {
# Use BLOCK_NAME.value or ITERATOR.value
attribute = BLOCK_NAME.value.something
}
}BLOCK_NAME — the nested block to generate (ingress, setting, tag, etc.)for_each — list or map to iterate overiterator — optional custom name (default: block name)content — the block body, with .value for current item and .key for index/keyAWS IAM Policy Statements:
variable "policy_statements" {
default = [
{
effect = "Allow"
actions = ["s3:GetObject", "s3:ListBucket"]
resources = ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*"]
},
{
effect = "Allow"
actions = ["dynamodb:GetItem", "dynamodb:PutItem"]
resources = ["arn:aws:dynamodb:*:*:table/my-table"]
}
]
}
data "aws_iam_policy_document" "app" {
dynamic "statement" {
for_each = var.policy_statements
content {
effect = statement.value.effect
actions = statement.value.actions
resources = statement.value.resources
}
}
}Azure Network Security Group Rules:
variable "nsg_rules" {
default = {
ssh = { priority = 100, port = 22, access = "Allow" }
http = { priority = 200, port = 80, access = "Allow" }
rdp = { priority = 300, port = 3389, access = "Deny" }
}
}
resource "azurerm_network_security_group" "web" {
name = "web-nsg"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
dynamic "security_rule" {
for_each = var.nsg_rules
content {
name = security_rule.key
priority = security_rule.value.priority
direction = "Inbound"
access = security_rule.value.access
protocol = "Tcp"
source_port_range = "*"
destination_port_range = security_rule.value.port
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
}Optional Blocks with Conditional:
variable "enable_encryption" {
type = bool
default = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
bucket = aws_s3_bucket.example.id
dynamic "rule" {
for_each = var.enable_encryption ? [1] : []
content {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
}The for_each = var.enable_encryption ? [1] : [] pattern creates 0 or 1 blocks based on a boolean.
For expressions transform and filter lists and maps inline.
# List → List
[for item in list : transform(item)]
# List → Map
{for item in list : key => value}
# With filter
[for item in list : transform(item) if condition]variable "names" {
default = ["alice", "bob", "charlie"]
}
locals {
# Uppercase
upper_names = [for name in var.names : upper(name)]
# Result: ["ALICE", "BOB", "CHARLIE"]
# Add prefix
prefixed = [for name in var.names : "user-${name}"]
# Result: ["user-alice", "user-bob", "user-charlie"]
}variable "instances" {
default = [
{ name = "web-1", type = "t3.micro", env = "prod" },
{ name = "web-2", type = "t3.small", env = "prod" },
{ name = "dev-1", type = "t3.micro", env = "dev" },
]
}
locals {
# Only prod instances
prod_instances = [for i in var.instances : i if i.env == "prod"]
# Result: [{name="web-1",...}, {name="web-2",...}]
# Just the names
prod_names = [for i in var.instances : i.name if i.env == "prod"]
# Result: ["web-1", "web-2"]
}variable "users" {
default = [
{ name = "alice", role = "admin" },
{ name = "bob", role = "developer" },
{ name = "carol", role = "admin" },
]
}
locals {
# Create a map keyed by name
user_map = {for user in var.users : user.name => user.role}
# Result: {alice = "admin", bob = "developer", carol = "admin"}
}variable "tags" {
default = {
Environment = "prod"
Team = "platform"
Project = "api"
}
}
locals {
# Prefix all tag keys
prefixed_tags = {for k, v in var.tags : "app:${k}" => v}
# Result: {"app:Environment" = "prod", "app:Team" = "platform", ...}
# Filter tags
non_env_tags = {for k, v in var.tags : k => v if k != "Environment"}
}variable "vpc_subnets" {
default = {
"us-east-1a" = ["10.0.1.0/24", "10.0.2.0/24"]
"us-east-1b" = ["10.0.3.0/24", "10.0.4.0/24"]
}
}
locals {
all_subnets = flatten([
for az, cidrs in var.vpc_subnets : [
for cidr in cidrs : {
az = az
cidr = cidr
}
]
])
# Result: [{az="us-east-1a", cidr="10.0.1.0/24"}, ...]
}
resource "aws_subnet" "all" {
for_each = {for s in local.all_subnets : "${s.az}-${s.cidr}" => s}
vpc_id = aws_vpc.main.id
availability_zone = each.value.az
cidr_block = each.value.cidr
}variable "services" {
default = {
web = { port = 80, public = true }
api = { port = 8080, public = false }
db = { port = 5432, public = false }
}
}
resource "aws_security_group" "app" {
name = "app-sg"
vpc_id = aws_vpc.main.id
# Only create ingress rules for public services
dynamic "ingress" {
for_each = {for k, v in var.services : k => v if v.public}
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = ingress.key
}
}
# Internal services accessible from VPC only
dynamic "ingress" {
for_each = {for k, v in var.services : k => v if !v.public}
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
description = "${ingress.key} (internal)"
}
}
}Dynamic blocks add complexity. Use them when you have 3+ repeated blocks driven by a variable. For 1-2 blocks, write them explicitly — it's more readable.
# Don't do this for 2 rules — just write them out
dynamic "ingress" {
for_each = [80, 443]
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Better: explicit blocks for 2 rules
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}Learn by doing with interactive courses on CopyPasteLearn:
Dynamic blocks replace repeated nested blocks with a single dynamic block that iterates over a variable. For expressions transform and filter lists and maps inline. Use them together to build flexible security groups, IAM policies, and multi-AZ subnets from variables. Keep it simple — if you only have 1-2 blocks, write them explicitly.
Install and run Terraform on Ubuntu 26.04 LTS Resolute Raccoon. Covers sudo-rs as default, APT 3.2 rollback, Kernel 7.0, Wayland-only, ROCm, and building...
Deploy serverless functions on Oracle Cloud with Terraform — applications, function deployment, and API Gateway. Step-by-step guide with code examples and be...
Set up OCI Load Balancer with Terraform — backend sets, listeners, SSL certificates, and health checks. Step-by-step guide with code examples and best practi...
Configure OCI Object Storage buckets with Terraform — lifecycle policies, pre-authenticated requests, and replication. Step-by-step guide with code examples ...