7.1 Input Variables, tfvars, and Precedence

Key Takeaways

  • Declare inputs with a variable block and read them as var.name; type, default, description, sensitive, nullable, validation, and ephemeral are the Terraform 1.12 arguments
  • Official assignment precedence, highest first: -var and -var-file (in the order given) plus HCP Terraform variables, then *.auto.tfvars lexical, terraform.tfvars.json, terraform.tfvars, TF_VAR_ environment variables, then the variable default
  • Do not commit secrets in .tfvars files; sensitive only redacts CLI output and still writes the value into state unless you mark the variable ephemeral
  • A backend block cannot refer to input variables, locals, or data sources — backend settings are not ordinary HCL expressions
  • A variable with no default is required; Terraform prompts for it before plan unless some other source supplies a value
Last updated: August 2026

7.1 Input Variables, tfvars, and Precedence

Quick Answer: Write variable "NAME" and read it as var.NAME. Root values come from -var / -var-file and HCP Terraform (highest), then *.auto.tfvars in lexical order, terraform.tfvars.json, terraform.tfvars, TF_VAR_ environment variables, and finally default. Do not put secrets in committed tfvars. sensitive hides CLI text but still stores the value in state. A backend block cannot use variables.

Objective 4c on Terraform Associate (004) is: use variables and outputs. This section is the input half. The product version on the exam is Terraform 1.12. HashiCorp's language pages to quote are Use input variables and the variable block reference. Full custom-condition design lives in chapter 9; here you only need the validation block shape.

Why a variable block exists

Hardcoded instance_type = "t2.micro" makes a module a one-off script. A variable block is the module's argument list: consumers pass values at run time without editing the module source. In the root module those values arrive from the CLI, files, environment, or HCP Terraform. In a child module they arrive only as arguments on the module block. Root terraform.tfvars does not automatically leak into a child — the parent must pass instance_type = var.instance_type.

variable "instance_type" {
  type        = string
  description = "EC2 instance type for the web server"
  default     = "t2.micro"
}

variable "subnet_id" {
  type        = string
  description = "Subnet ID where the web server will be deployed"
}

variable "environment" {
  type        = string
  description = "Deployment environment name"
  default     = "dev"

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

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  subnet_id     = var.subnet_id

  tags = {
    Environment = var.environment
    Name        = "${var.environment}-web-server"
  }
}

Reference syntax is always var.<NAME>. There is no variable.instance_type and no variables.instance_type. The label after variable must be unique in the module. Reserved names you cannot use: source, version, providers, count, for_each, lifecycle, depends_on, and locals.

subnet_id has no default, so it is required. Terraform prompts for it before it generates a plan unless another assignment source supplies a value. instance_type and environment are optional because they have defaults.

Arguments on the Terraform 1.12 variable block

ArgumentRequired?DefaultWhat 004 expects you to know
typeNoanyConstrains the assigned value. Omit it and the variable accepts any type. Prefer an exact constraint.
defaultNononeMakes the input optional. Must be a literal; it cannot reference other objects. Must convert to type.
descriptionNononeWritten for the module consumer, not as an author comment.
validationNononeNested block: condition must be true; error_message is required. Evaluated while Terraform creates a plan. Chapter 9 covers richer conditions.
sensitiveNofalseRedacts the value (and anything derived from it) in plan and apply logs. Still written to state.
nullableNotruefalse forbids null. When nullable = true and a default exists, an explicit null overrides the default. Nested null inside a list or object is still allowed if the collection itself is not null.
ephemeralNofalseTerraform 1.10+. Available at run time but omitted from state and plan files. Only usable in other ephemeral contexts (ephemeral outputs, ephemeral variables, write-only arguments, ephemeral resources, provider config, provisioners). Any expression that reads an ephemeral variable becomes ephemeral.

sensitive is not encryption. Anyone who can read terraform.tfstate can read the password. For a short-lived token you do not want in state, mark the variable ephemeral and keep it inside ephemeral contexts — that is the 004 distinction chapter 9 expands.

Assignment sources and official precedence

You cannot reassign the same variable twice inside one file. Across sources, HashiCorp's 1.12 order of precedence is (highest wins):

  1. Any -var and -var-file options on the command line, in the order provided, and variables from HCP Terraform.
  2. Any *.auto.tfvars or *.auto.tfvars.json files, in lexical order.
  3. The terraform.tfvars.json file.
  4. The terraform.tfvars file.
  5. Environment variables (TF_VAR_<name>).
  6. The default argument of the variable block.

Memorize that list. The exam loves swapping two adjacent rungs.

Terraform automatically loads terraform.tfvars, terraform.tfvars.json, and every *.auto.tfvars / *.auto.tfvars.json in the working directory. Any other filename — prod.tfvars, secret.tfvars — is ignored until you pass -var-file.

# prod.tfvars — not auto-loaded
instance_type      = "t3.large"
environment        = "prod"
subnet_ids         = ["subnet-12345", "subnet-67890"]
enable_monitoring  = true
terraform apply -var="instance_type=t3.medium" -var="environment=prod"
terraform apply -var-file="prod.tfvars"
export TF_VAR_instance_type=t3.medium
export TF_VAR_environment=staging
export TF_VAR_complex_config='{"key": "value", "list": ["a", "b"]}'

If both terraform.tfvars and prod.auto.tfvars set environment, the auto file wins. If you then pass -var="environment=lab", the CLI wins. If an HCP Terraform workspace variable is set, it sits at that same top rung as the CLI.

Complex values on the CLI or in TF_VAR_ need JSON (or careful HCL quoting). HashiCorp recommends a .tfvars file instead of fighting your shell.

JSON variable files use variable names as root keys:

{
  "image_id": "ami-abc123",
  "availability_zone_names": ["us-west-1a", "us-west-1c"]
}

Undeclared names

Terraform treats a value for a name that has no variable block differently by source:

  • TF_VAR_typoignored.
  • typo = "x" in a .tfvars file — warning (catches misspellings).
  • -var="typo=x"error.

Secrets, git, and the backend trap

If a .tfvars file holds passwords, tokens, or private keys, add it to .gitignore. Official style guidance: ignore variable definition files that contain sensitive values. Commit variables.tf (declarations) and maybe a terraform.tfvars.example with fake data. Do not commit secret.tfvars.

sensitive = true does not make a committed tfvars file safe. The value is still on disk in git history.

A backend block has a separate, exam-favorite restriction. Official backend language: a backend block cannot refer to named values (input variables, locals, or data source attributes). This is illegal:

terraform {
  backend "s3" {
    bucket = var.state_bucket   # error — not an ordinary expression
    key    = "app/terraform.tfstate"
    region = var.region
  }
}

Backend configuration is resolved at terraform init, before ordinary evaluation. Use a partial backend plus -backend-config, or the backend's own environment variables for credentials. Do not try to parameterize the backend with var..

004 traps for input variables

  • var.name is the only read syntax. local.name is a local. module.x.name is a child output.
  • default cannot be var.other or data.aws_ami.x.id.
  • terraform.tfvars loses to *.auto.tfvars, which loses to -var / -var-file / HCP Terraform.
  • TF_VAR_ is not the top of the list. It sits just above default.
  • sensitive is display-only. State still has the value unless the variable is ephemeral.
  • You cannot put var. inside backend.
  • Child modules do not read root tfvars. The parent passes arguments.
Loading diagram...
Terraform 1.12 input-variable precedence (highest wins)
Test Your Knowledge

A root module sets environment = "dev" in terraform.tfvars, environment = "staging" in prod.auto.tfvars, TF_VAR_environment=ci in the shell, and you run terraform apply -var="environment=lab". On Terraform 1.12, which value does var.environment receive?

A
B
C
D
Test Your Knowledge

Which statement about sensitive input variables and backend configuration is true on Terraform 1.12?

A
B
C
D
Test Your Knowledge

How do you reference a root input named image_id, and what happens if that variable has no default and no other assignment?

A
B
C
D