Terraform Cheatsheet

Variables

Use this Terraform reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Declaring Variables

# variables.tf
variable "instance_type" {
  description = "EC2 instance type for the web tier"
  type        = string
  default     = "t3.micro"
}

variable "environment" {
  description = "Deployment environment"
  type        = string
  # no default, so a value is required
}

variable "replica_count" {
  type    = number
  default = 1
}
ArgumentTypePurposeSince
typetype constraintreject wrong shapes early0.12
defaultexpressionmakes the variable optional0.x
descriptionstringdocuments the input0.x
validationblockcustom rule, may repeat0.13
sensitiveboolredact from CLI output0.14
nullableboolallow an explicit null1.1
ephemeralboolnever write to state or plan1.10
constboolusable at load time, e.g. module source1.15
deprecatedstringwarn when a caller sets it1.15

Names must be unique per module and cannot be source, version, providers, count, for_each, lifecycle, depends_on or locals.

The Type System

TypeExampleNotes
string"us-east-1"
number3, 3.14
booltrue"true" converts
list(string)["a", "b"]ordered, duplicates allowed, one element type
set(string)toset(["a", "b"])unordered, unique, one element type
map(string){ k = "v" }string keys, one value type
object({ ... })see belownamed attributes, mixed types, extra keys rejected
tuple([...])["x", 1, true]fixed length, positional, mixed types
anyanythingno checking, avoid it
variable "tags" {
  type    = map(string)
  default = { Project = "myapp", Env = "prod" }
}

variable "db_config" {
  type = object({
    engine        = string
    version       = string
    size          = number
    multi_az      = optional(bool, false)     # optional + default, 1.3+
    backup_window = optional(string)          # optional, defaults to null
  })
  default = {
    engine  = "postgres"
    version = "17"
    size    = 20
  }
}

variable "subnets" {
  type = list(object({
    name = string
    cidr = string
    tags = optional(map(string), {})
  }))
  default = []
}

optional(type, default) only works inside object({...}), and it composes: an optional attribute nested inside an optional object gets its default filled in when the parent is supplied. Prefer it over deep try() chains.

# Referencing
resource "aws_instance" "web" {
  instance_type = var.instance_type
  tags          = var.tags
}

engine_version = var.db_config.version           # object attribute
first_cidr     = var.subnets[0].cidr             # list index
maybe          = lookup(var.tags, "Owner", "-")  # map with default

Precedence Order

Highest wins. Later sources in each row override earlier ones.

#SourceNotes
1-var and -var-file on the command lineapplied in the order written, later wins, so -var does not automatically beat -var-file
2*.auto.tfvars and *.auto.tfvars.jsonauto-loaded, lexical filename order, later wins
3terraform.tfvars.jsonauto-loaded
4terraform.tfvarsauto-loaded
5TF_VAR_<name> environment variable
6default in the variable block
7interactive promptonly when nothing above supplies a value and stdin is a TTY
terraform apply -var="environment=prod" -var="replica_count=3"
terraform apply -var-file="envs/prod.tfvars"

# Order matters: prod.tfvars wins here
terraform apply -var="environment=dev" -var-file="envs/prod.tfvars"

export TF_VAR_environment=prod
export TF_VAR_tags='{ Project = "myapp" }'     # HCL syntax for complex types
  • -var-file is never auto-loaded from a subdirectory. Only terraform.tfvars and *.auto.tfvars in the working directory load themselves.
  • TF_VAR_ values are parsed as HCL for non-string types, and a malformed one fails at parse time with a confusing message.
  • Pass -input=false in CI so a missing required variable errors instead of hanging on a prompt.

tfvars Files

# terraform.tfvars (auto-loaded)
environment   = "prod"
instance_type = "t3.small"
replica_count = 2

tags = {
  Project = "myapp"
  Owner   = "platform-team"
}

allowed_cidrs = [
  "10.0.0.0/8",
  "192.168.0.0/16",
]

A .tfvars file is HCL but holds only name = value assignments. No blocks, no variable declarations, no interpolation of other variables.

Never commit a .tfvars file containing secrets. Add *.tfvars to .gitignore and commit a terraform.tfvars.example instead. Real secrets belong in a secrets manager, read at apply time through a data source or an ephemeral resource.

Validation

variable "environment" {
  type = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment must be dev, staging, or prod."
  }
}

variable "cidr_block" {
  type = string
  validation {
    condition     = can(cidrhost(var.cidr_block, 0))
    error_message = "cidr_block must be valid CIDR notation."
  }
}

Multiple validation blocks are allowed and all are evaluated, so one plan reports every problem.

Cross-variable validation (1.9+). Conditions are now ordinary expressions and may reference other variables, locals and data sources, not just the variable being validated.

variable "min_size" { type = number }

variable "max_size" {
  type = number
  validation {
    condition     = var.max_size >= var.min_size
    error_message = "max_size must be at least min_size."
  }
}

sensitive, nullable, ephemeral, const, deprecated

variable "db_password" {
  type      = string
  sensitive = true      # redacted in CLI output, still plaintext in state
}

variable "image_tag" {
  type     = string
  default  = "latest"
  nullable = true       # a caller may pass null and get the default
}

variable "vault_token" {
  type      = string
  ephemeral = true      # never written to state or plan files
  sensitive = true
}

variable "module_version" {
  type  = string
  const = true          # usable in module source/version, 1.15+
}

variable "old_name" {
  type       = string
  default    = null
  deprecated = "Use new_name instead. old_name is removed in v3."
}
export TF_VAR_db_password="$(vault kv get -field=password secret/db)"
  • sensitive hides values from output, it does not encrypt state. Assume state is a secret.
  • nullable = false (the default is true) rejects null outright, which is often what you want for a required setting.
  • ephemeral variables may only be consumed by provider config, provisioners, write-only arguments and other ephemeral values. Using one in a normal resource argument is an error.
  • deprecated emits a warning when a caller sets the variable, which is how you retire a module input without breaking anyone.