Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

Terraform Variable Types

Terraform’s type system lets you specify exactly what kind of data a variable accepts. This catches configuration mistakes before apply, provides better IDE support, and makes module interfaces self-documenting.


Primitive Types

string

variable "region" {
type = string
default = "us-east-1"
}
variable "cluster_name" {
type = string
description = "Name of the EKS cluster"
# No default — required input
}

number

variable "replica_count" {
type = number
default = 3
}
variable "disk_size_gb" {
type = number
default = 100
validation {
condition = var.disk_size_gb >= 20 && var.disk_size_gb <= 16384
error_message = "Disk size must be between 20 GB and 16,384 GB."
}
}

Numbers in Terraform are arbitrary-precision decimals — both 3 and 3.14 are valid number values.

bool

variable "enable_monitoring" {
type = bool
default = true
}
variable "multi_az" {
type = bool
default = false
}
# Usage in conditionals
resource "aws_db_instance" "main" {
multi_az = var.multi_az
monitoring_interval = var.enable_monitoring ? 60 : 0
}

Collection Types

list(T)

An ordered sequence of values of the same type:

variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
variable "allowed_ports" {
type = list(number)
default = [80, 443, 8080]
}
# Usage
resource "aws_subnet" "public" {
count = length(var.availability_zones)
availability_zone = var.availability_zones[count.index]
cidr_block = cidrsubnet("10.0.0.0/16", 8, count.index)
vpc_id = aws_vpc.main.id
}

set(T)

An unordered collection of unique values (no duplicates):

variable "allowed_account_ids" {
type = set(string)
default = ["123456789012", "234567890123"]
}
variable "enabled_regions" {
type = set(string)
default = ["us-east-1", "eu-west-1", "ap-southeast-1"]
}
# Use toset() to convert a list to a set for for_each
resource "aws_s3_bucket" "regional" {
for_each = var.enabled_regions
bucket = "mycompany-${each.key}-data"
}

map(T)

Key-value pairs where all values are the same type:

variable "instance_types_by_env" {
type = map(string)
default = {
dev = "t3.micro"
staging = "t3.small"
production = "t3.large"
}
}
variable "replica_count_by_env" {
type = map(number)
default = {
dev = 1
staging = 2
production = 5
}
}
# Usage
resource "aws_instance" "app" {
instance_type = lookup(var.instance_types_by_env, var.environment, "t3.micro")
}
# Or with map lookup
resource "aws_autoscaling_group" "app" {
desired_capacity = var.replica_count_by_env[var.environment]
}

Structural Types

object({})

Named attributes with potentially different types:

variable "database" {
type = object({
engine = string
engine_version = string
instance_class = string
allocated_storage = number
multi_az = bool
backup_days = number
})
default = {
engine = "postgres"
engine_version = "16.3"
instance_class = "db.t3.micro"
allocated_storage = 20
multi_az = false
backup_days = 7
}
}
resource "aws_db_instance" "main" {
engine = var.database.engine
engine_version = var.database.engine_version
instance_class = var.database.instance_class
allocated_storage = var.database.allocated_storage
multi_az = var.database.multi_az
backup_retention_period = var.database.backup_days
}

list(object({})) — Most Commonly Used Complex Type

variable "ingress_rules" {
type = list(object({
port = number
protocol = string
description = string
cidr_blocks = list(string)
}))
default = [
{
port = 443
protocol = "tcp"
description = "HTTPS"
cidr_blocks = ["0.0.0.0/0"]
},
{
port = 8080
protocol = "tcp"
description = "App port from ALB only"
cidr_blocks = ["10.0.0.0/8"]
}
]
}
resource "aws_security_group" "app" {
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
description = ingress.value.description
cidr_blocks = ingress.value.cidr_blocks
}
}
}

tuple

An ordered sequence where each position has a specific type (rare — usually list or object is better):

variable "config_pair" {
type = tuple([string, number])
default = ["t3.micro", 3]
}
# config_pair[0] = "t3.micro" (string)
# config_pair[1] = 3 (number)

The any Type

Disables type checking — use sparingly:

variable "flexible_config" {
type = any
default = {}
}

any is sometimes used in module variables where the exact type depends on the caller. Prefer explicit types — they catch mistakes early.


Type Conversion Functions

# Convert types when needed
locals {
# String to number
port_number = tonumber("8080")
# Number to string
port_string = tostring(8080)
# List to set (removes duplicates, loses order)
unique_regions = toset(["us-east-1", "eu-west-1", "us-east-1"])
# Result: {"eu-west-1", "us-east-1"}
# String to list
az_list = tolist(["a", "b", "c"])
}

Quick Reference

TypeExampleNotes
string"us-east-1"Unicode text
number42 or 3.14Arbitrary precision decimal
booltrue / falseBoolean
list(string)["a", "b", "c"]Ordered, allows duplicates
set(string)toset(["a", "b"])Unordered, unique values
map(string){key = "val"}String keys, same-type values
object({}){name = string, age = number}Named attributes, mixed types
tuple([])[string, number]Fixed-length, mixed types
anyAnythingDisables type checking