TL;DR
Master advanced Terraform features to write more efficient and maintainable infrastructure code
Advanced Terraform Features
Learn how to leverage advanced Terraform features to write more efficient, maintainable, and scalable infrastructure code.
$1
Workspaces allow you to manage multiple states for the same configuration.
$1
`` terraform workspace list terraform workspace new dev
terraform workspace new prod terraform workspace select dev
bash
`List workspaces
Create new workspace
Switch workspace
$1
` resource "aws_instance" "example" {
instance_type = terraform.workspace == "prod" ? "t3.medium" : "t2.micro"
tags = {
Environment = terraform.workspace
}
} locals {
environment_config = {
dev = {
instance_count = 1
instance_type = "t2.micro"
}
prod = {
instance_count = 3
instance_type = "t3.medium"
}
} config = local.environment_config[terraform.workspace]
}
hcl
`Use workspace name in configuration
Workspace-specific variables
$1
$1
` resource "aws_instance" "server" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "server-${count.index + 1}"
}
} resource "aws_eip" "lb" {
count = var.environment == "prod" ? 1 : 0
instance = aws_instance.server[count.index].id
}
hcl
`Basic count usage
Conditional creation
$1
` variable "instances" {
type = map(object({
instance_type = string
ami = string
}))
default = {
web = {
instance_type = "t2.micro"
ami = "ami-123"
}
app = {
instance_type = "t3.small"
ami = "ami-456"
}
}
} resource "aws_instance" "server" {
for_each = var.instances
ami = each.value.ami
instance_type = each.value.instance_type
tags = {
Name = "server-${each.key}"
}
} resource "aws_subnet" "example" {
for_each = toset(["us-west-2a", "us-west-2b", "us-west-2c"])
vpc_id = aws_vpc.main.id
availability_zone = each.key
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, index(tolist(toset(["us-west-2a", "us-west-2b", "us-west-2c"])), each.key))
}
hcl
`Using for_each with a map
Using for_each with a set
$1
$1
` variable "ingress_rules" {
type = list(object({
port = number
protocol = string
cidr_blocks = list(string)
}))
default = [
{
port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
]
} resource "aws_security_group" "example" {
name = "example"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
hcl
`
$1
` variable "load_balancer_config" {
type = object({
listeners = list(object({
port = number
protocol = string
rules = list(object({
priority = number
actions = list(map(string))
conditions = list(map(string))
}))
}))
})
} resource "aws_lb" "example" {
name = "example"
dynamic "listener" {
for_each = var.load_balancer_config.listeners
content {
port = listener.value.port
protocol = listener.value.protocol
dynamic "default_action" {
for_each = listener.value.rules
content {
type = "forward"
target_group_arn = aws_lb_target_group.example[default_action.key].arn
dynamic "condition" {
for_each = default_action.value.conditions
content {
host_header {
values = condition.value.values
}
}
}
}
}
}
}
}
hcl
`
$1
$1
` variable "environment" {
type = string
description = "Environment name"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
} variable "instance_type" {
type = string
description = "EC2 instance type"
validation {
condition = can(regex("^t[23].", var.instance_type))
error_message = "Instance type must be t2 or t3 series."
}
}
hcl
`
$1
` variable "vpc_config" {
type = object({
cidr_block = string
subnets = map(object({
cidr_block = string
public = bool
tags = map(string)
}))
enable_nat_gateway = bool
})
default = {
cidr_block = "10.0.0.0/16"
subnets = {
public-1 = {
cidr_block = "10.0.1.0/24"
public = true
tags = { Type = "Public" }
}
private-1 = {
cidr_block = "10.0.2.0/24"
public = false
tags = { Type = "Private" }
}
}
enable_nat_gateway = true
}
}
hcl
`
$1
$1
` output "instance_ids" {
value = aws_instance.server[*].id
} output "private_ips" {
value = [for i in aws_instance.server : i.private_ip if i.associate_public_ip_address]
}
hcl
`Get all instance IDs
Complex splat with for expressions
$1
` locals {
# Map transformation
instance_types = {
for k, v in var.instances : k => v.instance_type
}
# Filtering with for expressions
public_subnets = {
for k, v in var.vpc_config.subnets : k => v
if v.public
}
# Complex transformations
subnet_config = {
for az, config in var.vpc_config.subnets : az => {
cidr = config.cidr_block
tags = merge(config.tags, {
AZ = az
Environment = var.environment
})
}
}
}
hcl
`
$1
$1
` resource "aws_instance" "example" {
# ... other configuration ...
lifecycle {
create_before_destroy = true
prevent_destroy = true
ignore_changes = [
tags,
volume_tags,
]
precondition {
condition = var.environment != "prod" || var.instance_type != "t2.micro"
error_message = "Production environment cannot use t2.micro instances."
}
postcondition {
condition = self.associate_public_ip_address == false || var.environment != "prod"
error_message = "Production instances cannot have public IPs."
}
}
}
hcl
`
$1
$1
` data "aws_ami" "example" {
most_recent = true
dynamic "filter" {
for_each = var.ami_filters
content {
name = filter.value.name
values = filter.value.values
}
}
} resource "aws_instance" "example" {
count = length(var.subnet_ids)
ami = data.aws_ami.example.id
instance_type = var.instance_type
subnet_id = var.subnet_ids[count.index]
depends_on = [
aws_nat_gateway.example,
aws_route_table_association.private
]
}
hcl
``Dynamic data source queries
Depends on with count
$1
Advanced Terraform features enable:
Remember to:
1. Use workspaces for environment separation
2. Leverage count and for_each for resource replication
3. Implement dynamic blocks for repeated configurations
4. Utilize advanced variable validations
5. Apply proper lifecycle rules
Why This Matters
Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.
Trade-offs to Consider
Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.
When NOT to Use This
Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.
Decision Framework
Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.