Strongly-type a variable.
→Primitives: `string`, `number`, `bool`. Collections: `list(<type>)`, `set(<type>)`, `map(<type>)`. Structural: `object({...})`, `tuple([...])`. Use `any` only when truly polymorphic.
Reference↗
Restrict `environment` to dev/staging/prod.
→`variable "environment" { validation { condition = contains(["dev","staging","prod"], var.environment); error_message = "..." } }`. Multiple `validation` blocks allowed; all must pass.
Reference↗
Output a database password without it appearing in `terraform output`.
→`output "db_password" { value = ...; sensitive = true }`. CLI shows `(sensitive value)`. Still readable via `terraform output db_password` or `-json` (intentionally — for scripts).
Reference↗
Compute a value once and reuse it across many resources.
→`locals { common_tags = merge(var.tags, { Project = var.project }) }`. Reference as `local.common_tags`. Not overridable from outside the module.
Reference↗
Look up an existing AMI without managing it.
→`data "aws_ami" "ubuntu" { most_recent = true; filter { ... } }`. Read-only. Reference attributes as `data.aws_ami.ubuntu.id`.
Reference↗
Create N similar resources — pick `count` or `for_each`.
→`for_each` (with map or set) when items have stable identity (region names, environment keys). `count` for "I need N copies, order doesn't matter, identity is just an index". Adding/removing in the middle of `count` causes destroy/recreate; `for_each` preserves identity.
Reference↗
Create one resource per item in a list of strings.
→`for_each = toset(["a", "b", "c"])`. `each.key` and `each.value` both give the string. For maps: `for_each = var.users` — `each.key` = map key, `each.value` = map value.
Reference↗
Generate a variable number of nested blocks (e.g. ingress rules).
→`dynamic "ingress" { for_each = var.rules; content { from_port = ingress.value.from; ... } }`. The `iterator` argument can rename the iterator if needed.
Reference↗
Get a list of attributes across all instances of a `count` resource.
→`aws_instance.web[*].id` returns a list of IDs. Works with `count` and `for_each` (but `for_each` produces an unordered map, so `values(aws_instance.web)[*].id`).
Reference↗
Set a value based on a condition.
→Ternary: `var.is_prod ? 5 : 1`. Both branches must produce the same type.
Reference↗
Combine two maps; second's values win on key collision.
→`merge(local.defaults, local.overrides)`. Right-most map wins. Useful for tag composition.
Reference↗
Read a map value with a default if the key is missing.
→`lookup(var.config, "region", "us-east-1")`. Returns the default when the key isn't present. For deeply optional structures, prefer the optional `try()`.
Reference↗
Collapse nested lists into a flat list.
→`flatten([["a","b"], ["c"], [["d","e"]]])` → `["a","b","c","d","e"]`. Recursively flattens lists; preserves order.
Reference↗
Embed a Terraform map into an IAM policy document.
→`jsonencode({Version = "2012-10-17", Statement = [...]})`. Produces a JSON string. Inverse: `jsondecode()`.
Reference↗
Render a templated user-data script with values from variables.
→`templatefile("init.sh.tpl", { region = var.region, env = var.env })`. Template uses `${region}` syntax. Newer alternative for static rendering: `file()` + `format()`.
Reference↗
Carve subnet CIDRs out of a VPC range.
→`cidrsubnet("10.0.0.0/16", 8, 0)` → `10.0.0.0/24`. First arg = parent, second = bits to extend, third = subnet number.
Reference↗
Read a value that might not exist; fall back to a default.
→`try(yamldecode(file("config.yaml")), { defaults = true })`. Evaluates left-to-right; returns the first non-erroring expression.
Reference↗
Iterate over files matching a glob.
→`fileset(path.module, "configs/*.json")` returns a set of paths. Combine with `for_each` to manage one resource per file.
Reference↗
Same provider in two regions (e.g. AWS us-east-1 + us-west-2).
→Two `provider "aws" { alias = "..." }` blocks. Resources opt in via `provider = aws.us_west`. Modules accept aliases via `configuration_aliases`.
Reference↗
Run a shell command on a newly-created VM.
→`remote-exec` provisioner inside a resource. Last-resort tool — prefer cloud-init, user data, or configuration management. Provisioners aren't tracked in state and don't re-run on drift.
Reference↗
Run a command that doesn't correspond to a cloud resource (e.g. CLI invocation).
→`resource "null_resource" "trigger" { triggers = { sha = sha1(file(".")) }; provisioner "local-exec" { command = "..." } }`. Re-runs when `triggers` change.
Reference↗
Continuously verify a runtime invariant (e.g. health endpoint returns 200) without blocking apply.
→`check "endpoint" { data "http" "h" { url = "..." }; assert { condition = data.http.h.status_code == 200; error_message = "..." } }`. Runs at plan/apply; failure is a warning, not a hard error.
Why: `check` allows scoped data sources usable only inside the check. `precondition`/`postcondition` are hard errors at plan/apply.
Reference↗
Fail the plan if an AMI is too old.
→`lifecycle { precondition { condition = data.aws_ami.x.creation_date > "2024-01-01"; error_message = "..." } }`. Hard fail, evaluated before/after as appropriate.
Reference↗
Variable with a fixed-shape positional value.
→`type = tuple([string, number, bool])` requires exactly three elements in that order. Different from a list (homogenous, variable length).
Reference↗
Strongly-type a structured input (e.g. `database = { instance_class = string, allocated_storage = number }`).
→`type = object({ instance_class = string, allocated_storage = number, multi_az = optional(bool, false) })`. `optional(<type>, <default>)` makes attributes optional.
Reference↗
How are `*.tfvars` files loaded?
→`terraform.tfvars` and `*.auto.tfvars` load automatically (lexical order for `auto`). Other names require `-var-file=path.tfvars`.
Reference↗
Write automated tests for a Terraform module.
→`.tftest.hcl` files with `run "name" { command = plan|apply, assert { condition = ..., error_message = ... } }`. `command = plan` is no-side-effects; `command = apply` actually creates resources.
Reference↗
Pass variables into test runs.
→File-level `variables { ... }` block applies to all runs. Per-run `variables { ... }` overrides for that run only.
Reference↗