Skip to content

Input Variables

An input variable is a named, typed parameter that lets the same Terraform configuration produce different infrastructure depending on who calls it and with what values.

A variable block has four parts worth knowing well: type constrains what shape of value is acceptable, default supplies a fallback when nobody sets a value, description documents intent for the next reader, and sensitive redacts the value from most CLI output.

variable "environment" {
type = string
description = "Deployment environment name, e.g. dev, staging, prod"
default = "dev"
}
variable "instance_count" {
type = number
description = "Number of EC2 instances to launch"
default = 2
}
variable "enable_monitoring" {
type = bool
description = "Whether to enable detailed CloudWatch monitoring"
default = true
}
variable "allowed_ports" {
type = list(number)
description = "TCP ports allowed inbound on the security group"
default = [22, 443]
}
variable "resource_tags" {
type = map(string)
description = "Tags applied to every resource in this configuration"
default = {
project = "payments"
team = "platform"
}
}
variable "db_config" {
type = object({
engine = string
instance_class = string
allocated_gb = number
})
description = "Structured configuration for the RDS instance"
default = {
engine = "postgres"
instance_class = "db.t3.micro"
allocated_gb = 20
}
}
variable "db_password" {
type = string
description = "Master password for the RDS instance"
sensitive = true
}

object({...}) is worth calling out: it is a typed, fixed-shape structure — every key you list is required (unless you give it a default with optional()), and Terraform rejects a caller-supplied value that has the wrong shape at plan time, well before any AWS API call happens.

The sensitive = true argument on db_password only affects display. Terraform masks the value in plan/apply output and in the CLI as (sensitive value). It does not encrypt or omit the value from the state file — the state-management module already covered this: state stores every attribute of every managed resource in plain text inside the JSON, including values that came from a sensitive variable. Treat sensitive = true as a display protection for terminals and CI logs, not as a substitute for a properly access-controlled remote backend.

Validating a variable with a validation block

Section titled “Validating a variable with a validation block”

A default or a type constraint only checks the shape of a value, not whether it makes sense. A validation block adds a custom condition and a specific error_message, so a caller gets immediate, actionable feedback at plan time instead of a confusing error from deep inside the AWS provider.

variable "instance_type" {
type = string
description = "EC2 instance type, must be from the approved t3/m6i families"
validation {
condition = can(regex("^(t3|m6i)\\.(micro|small|medium|large|xlarge)$", var.instance_type))
error_message = "instance_type must be a t3 or m6i size, e.g. t3.micro or m6i.large."
}
}
variable "environment" {
type = string
description = "Deployment environment name"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment must be one of: dev, staging, prod."
}
}

Without that first validation block, passing instance_type = "x1.enormous" would sail through terraform plan and only fail once Terraform actually calls the EC2 API to create the instance — a much slower feedback loop, and a much less specific error message than “instance_type must be a t3 or m6i size.” The can() wrapper turns a regex() failure into false instead of an outright error, which is exactly what a condition expression needs: it must always evaluate to true or false, never raise.

Terraform lets you set a value for the same variable from several places at once, and it resolves conflicts with a strict, well-defined precedence. From highest priority (wins) to lowest:

  1. -var and -var-file on the command line, in the order given — the most explicit, most temporary way to set a value.
  2. *.auto.tfvars / *.auto.tfvars.json files, auto-loaded from the working directory in lexical (alphabetical) order of filename.
  3. terraform.tfvars / terraform.tfvars.json, auto-loaded if present.
  4. TF_VAR_<name> environment variables — useful for CI pipelines and for values you do not want sitting in a file at all.
  5. The variable’s own default — used only if nothing above supplied a value.
Terminal window
# Highest precedence: an explicit CLI flag for this one run
terraform apply -var="instance_type=m6i.large"
# Or point at a specific tfvars file
terraform apply -var-file="prod.tfvars"
# Environment variable equivalent of setting instance_type
export TF_VAR_instance_type="t3.medium"
terraform apply

If instance_type is set in terraform.tfvars, again in prod.auto.tfvars, and again via -var on the command line, the -var value wins — Terraform does not merge scalar values across sources, it picks the highest-priority one that exists. Collection types like maps and objects are the exception worth remembering later: some tooling merges nested values rather than fully overriding, but for the plain sources above, a defined value at a higher-priority source simply replaces a lower-priority one for that variable.

flowchart LR
  a["-var / -var-file (CLI flag)"] --> r["Resolved value of var.instance_type"]
  b["*.auto.tfvars (alphabetical)"] --> r
  c["terraform.tfvars"] --> r
  d["TF_VAR_instance_type (env var)"] --> r
  e["default in the variable block"] --> r
  r --> res["aws_instance.web"]
Multiple value sources resolve to one value per variable, by precedence
What does a validation block on a variable actually prevent?
If a variable has a value in terraform.tfvars, in a TF_VAR_ environment variable, and passed via -var on the command line, which value does Terraform use?
What does sensitive = true on a variable actually protect?
Between *.auto.tfvars files and terraform.tfvars, which one wins if both set the same variable?