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
7.1 Input Variables, tfvars, and Precedence
Quick Answer: Write
variable "NAME"and read it asvar.NAME. Root values come from-var/-var-fileand HCP Terraform (highest), then*.auto.tfvarsin lexical order,terraform.tfvars.json,terraform.tfvars,TF_VAR_environment variables, and finallydefault. Do not put secrets in committed tfvars.sensitivehides CLI text but still stores the value in state. Abackendblock 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
| Argument | Required? | Default | What 004 expects you to know |
|---|---|---|---|
type | No | any | Constrains the assigned value. Omit it and the variable accepts any type. Prefer an exact constraint. |
default | No | none | Makes the input optional. Must be a literal; it cannot reference other objects. Must convert to type. |
description | No | none | Written for the module consumer, not as an author comment. |
validation | No | none | Nested block: condition must be true; error_message is required. Evaluated while Terraform creates a plan. Chapter 9 covers richer conditions. |
sensitive | No | false | Redacts the value (and anything derived from it) in plan and apply logs. Still written to state. |
nullable | No | true | false 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. |
ephemeral | No | false | Terraform 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):
- Any
-varand-var-fileoptions on the command line, in the order provided, and variables from HCP Terraform. - Any
*.auto.tfvarsor*.auto.tfvars.jsonfiles, in lexical order. - The
terraform.tfvars.jsonfile. - The
terraform.tfvarsfile. - Environment variables (
TF_VAR_<name>). - The
defaultargument of thevariableblock.
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_typo— ignored.typo = "x"in a.tfvarsfile — 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.nameis the only read syntax.local.nameis a local.module.x.nameis a child output.defaultcannot bevar.otherordata.aws_ami.x.id.terraform.tfvarsloses to*.auto.tfvars, which loses to-var/-var-file/ HCP Terraform.TF_VAR_is not the top of the list. It sits just abovedefault.sensitiveis display-only. State still has the value unless the variable isephemeral.- You cannot put
var.insidebackend. - Child modules do not read root tfvars. The parent passes arguments.
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?
Which statement about sensitive input variables and backend configuration is true on Terraform 1.12?
How do you reference a root input named image_id, and what happens if that variable has no default and no other assignment?