Terraform Cheatsheet
Expressions and Functions
Use this Terraform reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Types of Expressions
# Literal values 42 true "hello" ["a", "b", "c"] { key = "value" } # References var.instance_type local.name_prefix aws_instance.web.public_ip module.vpc.vpc_id data.aws_ami.ubuntu.id count.index each.key / each.value path.module / path.root / path.cwd terraform.workspace
String Interpolation and Templates
name = "server-${var.environment}-${count.index}" endpoint = "https://${aws_lb.api.dns_name}/api" # Heredoc user_data = <<-EOT #!/bin/bash echo "ENV=${var.environment}" >> /etc/app.conf systemctl start myapp EOT # Heredoc with template directives hosts = <<-EOT %{ for ip in var.private_ips ~} ${ip} %{ endfor ~} EOT
Conditional Expression (ternary)
instance_type = var.is_prod ? "t3.large" : "t3.micro" count = var.enable_replica ? 2 : 0 subnet_id = var.use_private ? aws_subnet.private.id : aws_subnet.public.id
for Expressions
# List → list upper_names = [for s in var.names : upper(s)] # List → list with filter long_names = [for s in var.names : s if length(s) > 5] # Map → map upper_tags = { for k, v in var.tags : k => upper(v) } # Map → list tag_strings = [for k, v in var.tags : "${k}=${v}"] # List → map (index as key) indexed = { for i, v in var.names : i => v } # Resource for_each → map output instance_ids = { for k, v in aws_instance.app : k => v.id }
Splat Expressions
# Full splat — [*] — the modern form (shorthand for a for expression) all_ips = aws_instance.web[*].private_ip # Equivalent: [for o in aws_instance.web : o.private_ip] # Splats don't nest — [*] only applies at one level. # For list-of-lists attributes, flatten the result: all_sg_ids = flatten(aws_instance.web[*].vpc_security_group_ids) # Legacy attribute-only splat (pre-0.12) — avoid in new code all_ips_legacy = aws_instance.web.*.private_ip
try() and can()
# try() — returns the first argument that evaluates without error image = try(var.config.image, "nginx:latest") port = try(var.listeners[0].port, 8080) # can() — true if the expression evaluates without error validation { condition = can(regex("^ami-", var.ami_id)) error_message = "ami_id must start with \"ami-\"." }
For optional object attributes with defaults, prefer
optional(type, default)in the variable type over deeptry()chains.
Date/Time and ID Functions
| Function | Example | Notes |
|---|---|---|
timestamp() | "2026-07-05T12:00:00Z" | changes every apply — pair with ignore_changes |
plantimestamp() | fixed at plan time | stable within one plan/apply (1.5+) |
formatdate(spec, ts) | formatdate("YYYY-MM-DD", timestamp()) | |
timeadd(ts, dur) | timeadd(timestamp(), "24h") | |
timecmp(a, b) | -1 / 0 / 1 | compare timestamps |
uuid() | random UUID | new every run — prefer the random_uuid resource |
String Functions
| Function | Example | Result |
|---|---|---|
upper(s) | upper("hello") | "HELLO" |
lower(s) | lower("WORLD") | "world" |
trimspace(s) | trimspace(" hi ") | "hi" |
trim(s, cutset) | trim("!!hi!!", "!") | "hi" |
chomp(s) | removes trailing newline | |
split(sep, s) | split(",", "a,b,c") | ["a","b","c"] |
join(sep, list) | join(", ", ["a","b"]) | "a, b" |
replace(s, sub, rep) | replace("a-b", "-", "_") | "a_b" |
format(fmt, ...) | format("%s-%d", "app", 1) | "app-1" |
formatlist(fmt, list) | formatlist("item-%s", ["a","b"]) | ["item-a","item-b"] |
substr(s, off, len) | substr("hello", 1, 3) | "ell" |
startswith(s, prefix) | startswith("hello", "he") | true |
endswith(s, suffix) | endswith("hello", "lo") | true |
regex(pattern, s) | match and capture groups | |
regexall(pattern, s) | all matches as list |
Number Functions
| Function | Example | Result |
|---|---|---|
abs(n) | abs(-5) | 5 |
ceil(n) | ceil(1.2) | 2 |
floor(n) | floor(1.9) | 1 |
max(...) | max(3, 1, 4) | 4 |
min(...) | min(3, 1, 4) | 1 |
pow(x, y) | pow(2, 8) | 256 |
signum(n) | signum(-3) | -1 |
Collection Functions
| Function | Example | Result |
|---|---|---|
length(v) | length([1,2,3]) | 3 |
concat(a, b) | concat(["a"], ["b"]) | ["a","b"] |
flatten(list) | flatten([[1,2],[3]]) | [1,2,3] |
distinct(list) | distinct([1,1,2]) | [1,2] |
compact(list) | removes null/"" | |
slice(list, s, e) | slice([1,2,3,4], 1, 3) | [2,3] |
reverse(list) | reverse([1,2,3]) | [3,2,1] |
sort(list) | lexicographic sort | |
contains(list, v) | contains(["a","b"], "a") | true |
index(list, v) | position of first match | |
element(list, i) | element(["a","b"], 2) | "a" (wraps) |
keys(map) | sorted list of keys | |
values(map) | list of values (sorted by key) | |
lookup(map, k, d) | lookup(m, "x", "default") | |
merge(m1, m2) | merge maps (m2 wins) | |
zipmap(keys, vals) | construct map from two lists | |
toset(list) | toset(["a","a","b"]) | {"a","b"} |
tolist(set) | convert set to list | |
tomap(obj) | convert object to map |
Type Conversion
tostring(42) # "42" tonumber("42") # 42 tobool("true") # true tolist(toset(["a"])) # ["a"]
Encoding Functions
| Function | Use |
|---|---|
jsonencode(val) | encode value as JSON string |
jsondecode(str) | parse JSON string |
yamlencode(val) | encode as YAML |
yamldecode(str) | parse YAML |
base64encode(str) | base64 encode |
base64decode(str) | base64 decode |
base64gzip(str) | gzip then base64 |
filebase64(path) | read file and base64 encode |
Filesystem Functions
file("config/app.conf") # read file as string filebase64("certs/ca.pem") # read and base64 encode fileset(path.module, "files/*") # list files matching glob # templatefile() — render a template file with variables user_data = templatefile("${path.module}/init.tftpl", { env = var.environment port = 8080 })
# init.tftpl — same ${} interpolation and %{ for }/%{ if } directives as heredocs #!/bin/bash echo "ENV=${env}" >> /etc/app.conf systemctl start "app-${port}"
IP / CIDR Functions
cidrsubnet("10.0.0.0/16", 8, 0) # => "10.0.0.0/24" cidrsubnet("10.0.0.0/16", 8, 1) # => "10.0.1.0/24" cidrhost("10.0.0.0/24", 5) # => "10.0.0.5" cidrnetmask("10.0.0.0/24") # => "255.255.255.0"
Useful Patterns
# Default value if null name = coalesce(var.override_name, "default-name") # First non-null, non-empty from list region = coalesce(var.region, "us-east-1") # Conditional resource creation count = var.create_bucket ? 1 : 0 # Map lookup with default size = lookup(local.size_map, var.environment, "t3.micro") # Build subnet CIDRs dynamically subnets = [for i in range(3) : cidrsubnet(var.vpc_cidr, 8, i)] # Merge with override final_tags = merge(local.default_tags, var.extra_tags) # one() — assert list has exactly one element vpc_id = one(data.aws_vpcs.selected.ids)