Domain 4 of 8 · Chapter 3 of 8

Variables and outputs

Declaring input variables

A variable block declares an input variable, one value a module (a directory of configuration Terraform evaluates as a unit) accepts from its caller, and optionally constrains and documents it:

variable "instance_count" {
  type        = number
  description = "How many app servers to create"
  default     = 2
}

variable "db_password" {
  type      = string
  sensitive = true
}

The label after variable is the variable's name, and inside the module you read its value as var.instance_count, never as the bare instance_count. Referring to it by the bare name is a syntax error, not a silent fall-through to null. Everything else in the block is optional and shapes how a value is accepted. Declaring a variable, choosing where its value comes from, and handing results back out through output blocks are the three moves this page walks through.

The arguments a variable block accepts

A variable block supports a fixed set of optional arguments[1]: type constrains the accepted value (a string, number, bool, or a complex type), description documents it, default supplies a fallback, sensitive = true keeps the value out of CLI output, and nullable controls whether null is an acceptable value. Terraform v1.10 added one more, ephemeral[2], which makes a value available at runtime while omitting it from state and plan files; it belongs with managing sensitive data. A nested validation block adds custom rules such as an allowed range or a required prefix; that mechanism has its own page, custom condition validation, so here it is enough to know it lives inside the variable block.

Required versus optional

Whether a variable is required comes down to one thing: does it declare a default[1]. A variable with a default is optional, and Terraform uses that value when no source supplies one. A variable with no default is required. In an interactive run Terraform prompts for the value before it produces a plan; in a non-interactive run, such as one started with -input=false or most automation, the missing value is an error and Terraform stops. The figure summarizes that resolution: a value set by any source is used, otherwise the default, otherwise the required-value prompt or error.

Null values and nullable

By default nullable is true, so a caller may assign null. Setting nullable = false[1] makes null an invalid value for that variable, and Terraform rejects any attempt to set it to null. You write nullable only when you want to reject null, since the permissive default already allows it.

Any source sets the variable?The set value is usedYesNoDeclares a default?The default is usedYesNoRequired valueprompt, or error under -input=false
How Terraform resolves a variable value: a value from any source wins, then the default, then a required prompt or an error under -input=false.

Setting values and precedence

A required variable, or one whose default you want to override, takes its value from one of a few sources, and when two sources disagree a fixed precedence decides the winner.

Where a value can come from

Terraform accepts a variable's value from the command line, variable files, and the environment[1]:

  • -var on the command line: terraform apply -var="region=us-east-1". It is repeatable, and among command-line options the last one to set a given variable wins.
  • -var-file on the command line: terraform apply -var-file="prod.tfvars", loading a whole file of assignments at once.
  • .tfvars files: a file of name = value assignments. Which of these load on their own is the next subsection.
  • TF_VAR_ environment variables: an environment variable TF_VAR_region=us-east-1[1] sets the input variable region. This is the common way to feed a secret or a CI value without writing it into a file.
  • The default: the fallback declared in the variable block, used only when nothing else sets the value.

Which files auto-load

Terraform loads three kinds of file from the working directory with no flag[1]: terraform.tfvars, terraform.tfvars.json, and every file whose name ends in .auto.tfvars or .auto.tfvars.json. Any other name is invisible unless you pass it with -var-file. So terraform.tfvars and prod.auto.tfvars load by themselves, while prod.tfvars loads only via terraform apply -var-file=prod.tfvars. The misconception runs the other way just as often: terraform.tfvars does not need -var-file, it is picked up automatically.

Precedence, highest to lowest

When more than one source sets the same variable, Terraform applies this precedence, from highest to lowest[1]:

  1. -var and -var-file on the command line, in the order given (later wins).
  2. *.auto.tfvars and *.auto.tfvars.json, processed in lexical filename order.
  3. terraform.tfvars.json.
  4. terraform.tfvars.
  5. TF_VAR_ environment variables.
  6. The default in the variable block.

Read the ladder in this direction: a command-line -var beats every file, any .tfvars file beats an environment variable, and the default is the last resort. A common trap reverses part of it, claiming a TF_VAR_ value or a terraform.tfvars entry overrides a -var given on the same run. It does not: the command line always wins.

highestlowest1 -var / -var-file (last wins)2 *.auto.tfvars(.json) lexical order3 terraform.tfvars.json4 terraform.tfvars5 TF_VAR_<name> (environment)6 default (in the variable block)
Variable value precedence, highest first: command-line flags, then auto-loaded tfvars files, then TF_VAR_ environment variables, then the default.

Output values

Outputs are the other half of a module's interface: an output block publishes a value so a parent module can read it, or so an operator can see it after an apply.

Declaring an output

An output block[3] has one required argument, value, set to any valid expression, plus optional arguments such as description, sensitive, depends_on, and precondition:

output "instance_ip" {
  value       = aws_instance.web.private_ip
  description = "Private IP of the app server"
}

A root-module output like this is printed after terraform apply[3] and is available from the terraform output command[4]: plain terraform output prints them all, terraform output instance_ip prints one, and -json or -raw format the result for scripts.

Outputs are how modules expose data

A child module's resources are private to it. The only way a parent reads data a child produced is through an output the child declares[3], addressed as module.<MODULE_NAME>.<OUTPUT_NAME>. Reaching for a child's resource directly, such as module.web.aws_instance.this.id, does not work: the child must first surface it with an output, after which the parent reads module.web.instance_id. The figure traces one value from a child resource up to the command line.

Sensitive outputs hide the console, not the state

Marking an output sensitive = true[3] changes exactly one thing: Terraform prints <sensitive> in place of the value in plan, apply, and terraform output display. It does not encrypt the value or keep it out of state. Terraform stores the values of sensitive outputs in state[3] in plain text, and terraform output -json or terraform output -raw NAME print them unredacted. So sensitive guards against a shoulder-surfer or a leaked CI log, not against state exposure, which is why sensitive state belongs in a secured, access-controlled backend (see managing sensitive data). One related rule follows from the same design: if an output's value references a value Terraform already treats as sensitive, you must mark the output sensitive too, or Terraform reports an Output refers to sensitive values error[5].

Resource attributeproduced inside the child moduleChild module outputoutput block: value = attributemodule.<NAME>.<OUTPUT>parent reads only declared outputsRoot module outputoutput block: value = module.<NAME>.xterraform outputafter apply; -json / -raw for scripts
A value flows out of a module: child resource attribute, child output, parent module.NAME.OUTPUT reference, root output, then the terraform output command.

How this appears on the exam

This objective is tested less by definitions than by spotting the plausible-but-wrong option. Each trap below is a restatement of a rule established above:

  • Referencing a variable. A stem uses the bare name, region, where the module body needs var.region. Inside configuration a variable is always var.<NAME>; the bare name is a syntax error.
  • Required versus optional. "What happens to a variable with no default when nothing sets it?" In an interactive run Terraform prompts; under -input=false or automation it errors. It never silently becomes null or empty.
  • Which files auto-load. terraform.tfvars, terraform.tfvars.json, and *.auto.tfvars(.json) load with no flag; prod.tfvars loads only with -var-file. The distractor claims terraform.tfvars needs -var-file, or that only *.auto.tfvars files auto-load.
  • Precedence direction. "A variable is set by both a -var flag and terraform.tfvars; which applies?" The command line wins. Any answer where terraform.tfvars or a TF_VAR_ value overrides -var is wrong.
  • Reading a child module. The parent reads module.<NAME>.<OUTPUT>, and only declared outputs are visible. "Reference the module's resource directly" is the wrong answer; the module must expose it with an output first.
  • Sensitive outputs. sensitive = true redacts the console display as <sensitive>, but the value is still in state in plain text and terraform output -json/-raw reveal it. "It is omitted from or encrypted in state" is the distractor.

Variable value sources, by precedence

SourceHow it is setAuto-loadedPrecedence (1 = wins)
`-var` / `-var-file`Passed on the CLI; last occurrence winsNo, always explicit1 (highest)
`*.auto.tfvars(.json)`Any file with that suffix in the directoryYes, in lexical order2
`terraform.tfvars.json`That exact filenameYes3
`terraform.tfvars`That exact filenameYes4
`TF_VAR_<name>`Environment variableYes, from the environment5
`default``default` in the `variable` blockn/a6 (lowest)

Decision tree

Secret passed at run time?TF_VAR_<name> / -varYesNoDiffers per environment?-var-file / *.auto.tfvarsYesNoSafe fallback for all callers?default (variable optional)YesNoterraform.tfvarsset the value explicitly for this directory

Sharp facts the exam loves — give these one last read before exam day.

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Variables without default are required

A variable block with no default is required; Terraform prompts for its value interactively, or errors in a non-interactive run, when it is not supplied.

Trap Assuming a variable without a default silently becomes null or empty.

4 questions test this
Variable type and nullable

type constrains the accepted value, default supplies a fallback, and nullable = false forbids a null value (the default is nullable = true).

4 questions test this
Access variables with var.

Inside configuration a variable's value is accessed as var.<NAME>, never by the bare variable name.

Trap Referencing a variable as vpc_cidrs instead of var.vpc_cidrs.

4 questions test this
Which tfvars auto-load

Terraform automatically loads terraform.tfvars, terraform.tfvars.json, and any *.auto.tfvars/*.auto.tfvars.json files from the working directory with no extra flags; other .tfvars files must be passed explicitly with -var-file.

Trap Thinking terraform.tfvars is ignored unless passed with -var-file, or that only *.auto.tfvars files auto-load.

8 questions test this
TF_VAR_ environment variables

An environment variable named TF_VAR_<name> sets the value of the input variable <name>.

6 questions test this
Variable precedence order

When a variable is set in multiple places, precedence from highest to lowest is: -var/-var-file (command line, last one wins), *.auto.tfvars (processed in lexical filename order), terraform.tfvars.json, terraform.tfvars, TF_VAR_ environment variables, then the default.

Trap Believing environment variables or terraform.tfvars override a command-line -var value.

8 questions test this
Output block value argument

An output block exposes data through its required value argument; root-module outputs are printed after apply and by the terraform output command.

8 questions test this
Accessing child module outputs

A parent module reads a child module's output as module.<MODULE_NAME>.<OUTPUT_NAME>; only declared outputs are exposed, not the child's internal resources.

Trap Attempting to reference a child module's resource directly instead of through a declared output.

2 questions test this
Sensitive output behavior

sensitive = true on an output redacts it as <sensitive> in CLI output but the value is still stored in state, and terraform output -json/-raw reveal it.

Trap Assuming a sensitive output is omitted from or encrypted in the state file.

4 questions test this

References

  1. Input Variables
  2. The variable block reference
  3. Output Values
  4. Command: terraform output
  5. Protect sensitive input variables