Terraform
TerraformIntermediate

Advanced Terraform Features - Workspaces, Count, and Dynamic Blocks

DevHub Team
5 min read
IaCDevOpsAdvanced

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

``bash

List workspaces

terraform workspace list

Create new workspace

terraform workspace new dev

terraform workspace new prod

Switch workspace

terraform workspace select dev

`

$1

`hcl

Use workspace name in configuration

resource "aws_instance" "example" {

instance_type = terraform.workspace == "prod" ? "t3.medium" : "t2.micro"

tags = {

Environment = terraform.workspace

}

}

Workspace-specific variables

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]

}

`

$1

$1

`hcl

Basic count usage

resource "aws_instance" "server" {

count = 3

ami = "ami-0c55b159cbfafe1f0"

instance_type = "t2.micro"

tags = {

Name = "server-${count.index + 1}"

}

}

Conditional creation

resource "aws_eip" "lb" {

count = var.environment == "prod" ? 1 : 0

instance = aws_instance.server[count.index].id

}

`

$1

`hcl

Using for_each with a map

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}"

}

}

Using for_each with a set

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))

}

`

$1

$1

`hcl

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

}

}

}

`

$1

`hcl

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

}

}

}

}

}

}

}

}

`

$1

$1

`hcl

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."

}

}

`

$1

`hcl

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

}

}

`

$1

$1

`hcl

Get all instance IDs

output "instance_ids" {

value = aws_instance.server[*].id

}

Complex splat with for expressions

output "private_ips" {

value = [for i in aws_instance.server : i.private_ip if i.associate_public_ip_address]

}

`

$1

`hcl

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

})

}

}

}

`

$1

$1

`hcl

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."

}

}

}

`

$1

$1

`hcl

Dynamic data source queries

data "aws_ami" "example" {

most_recent = true

dynamic "filter" {

for_each = var.ami_filters

content {

name = filter.value.name

values = filter.value.values

}

}

}

Depends on with count

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

]

}

``

$1

Advanced Terraform features enable:

  • Better environment management
  • More efficient resource creation
  • Dynamic and flexible configurations
  • Complex infrastructure patterns
  • Improved code maintainability
  • 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.