TerraformXIII · Variables, Outputs, and LocalsProduction Terraform
Variable Input Precedence
What you'll learn
- State the precedence order from CLI flag to default value
- Predict the resolved value when multiple sources define the same variable
- Choose the right source per environment and per CI pipeline
- Recognise the failure modes of mixed sources and tfvars in Git
- Audit a configuration to find every source that sets a variable
Prerequisites
None — start here.
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13
Production applies do not run with a single source of values. A region
might come from a .tfvars file in Git, an instance count might come
from a CI -var flag, and a backup retention period might come from an
environment variable. Terraform resolves all of them in a defined order.
The operator who does not know the order is the operator who is surprised
when the wrong value lands in production.
A real production incident: the team kept environment variables in
.envrc for local dev. One engineer ran terraform plan with
TF_VAR_region=us-east-1 still exported. The plan produced resources
in us-east-1 instead of eu-west-2, was approved, and the apply
created 80 resources in the wrong region. The fix was $ unset TF_VAR_region and a CI check that fails the build when TF_VAR_* is set.
The precedence order
Terraform consults variable sources in a fixed order. The first source that supplies a value wins; later sources are ignored for that variable.
Highest -var CLI flag (and -var-file)
|
*.auto.tfvars (alphabetical: a- < b- < c-)
|
terraform.tfvars (a specific filename)
|
TF_VAR_<name> environment variable
|
Lowest default in the variable block
# CONFIGURATION
# variables.tf
variable "region" {
type = string
description = "AWS region for all resources in this configuration."
default = "eu-west-2"
}
variable "instance_count" {
type = number
description = "Number of application instances."
default = 2
}
Demonstration across three sources
Same region variable, three invocation patterns, three resolved values:
# READ-ONLY — auto-loaded *.auto.tfvars provides region
# Files on disk: production.auto.tfvars with region = "eu-west-2"
terraform plan
# Resolves: region = eu-west-2, instance_count = 2 (from default)
# CONFIGURATION — environment variable overrides auto-loaded
TF_VAR_region="us-east-1" terraform plan
# Resolves: region = us-east-1, instance_count = 2
# CONFIGURATION — CLI flag wins over both
terraform plan -var="region=ap-southeast-2" -var="instance_count=4"
# Resolves: region = ap-southeast-2, instance_count = 4
The -var-file flag is at the same precedence as -var, not above it.
Two -var-file flags are merged, with the later one winning for
overlapping keys.
Auto-loaded tfvars files
Terraform loads two filename patterns automatically:
terraform.tfvars(a single specific filename)- Any file matching
*.auto.tfvarsor*.auto.tfvars.json
The auto-loaded files are read in alphabetical order. If two files set the same variable, the file sorted later wins. The order is not the modification time — it is the filename, lexicographically.
Loading order (alphabetical):
alpha.auto.tfvars -> sets region = "eu-west-1"
beta.auto.tfvars -> sets region = "us-east-1"
production.auto.tfvars -> sets region = "eu-west-2" (winner)
terraform.tfvars -> sets instance_count = 6
A subtle production trap: production.auto.tfvars is alphabetically
before staging.auto.tfvars, but the operator expects production to
win because it is production. The filename is the source of truth, not
the environment name. If two files both define region, the
alphabetically-later one wins.
WhyThisMatters
WhyThisMatters CI pipelines fail closed when the precedence is right.
A pipeline that runs terraform plan against main with no
-var-file flag is silently reading whatever *.auto.tfvars files the
checkout happens to contain. The plan looks correct. The apply lands in
the wrong account. The fix is explicit: every CI invocation passes
-var-file=environments/production.tfvars and the file is fetched from
a secrets manager.
Failure modes
-
Local checkout has
prod.auto.tfvars; CI does not. Engineer A’sterraform planshows resources ineu-west-2. The same plan in CI fails because the file is in.gitignore. Precedence is unchanged; the source is missing in CI. -
TF_VAR_*leaked into shell from a previous session. Engineer B opens a new terminal, runsterraform plan, and the env var from a different project overrides the-var-file. The plan succeeds with values from the wrong source. -
Two
*.auto.tfvarsfiles set the same variable. Engineer C expectsproduction.auto.tfvarsto win;staging.auto.tfvarsdoes win because it sorts later. The apply lands in staging. -
-var-fileand-varboth set the same variable. Engineer D passes-var-file=prod.tfvarsand-var=region=eu-west-2. The CLI-varwins. The engineer assumed the file wins because it is “the production config.” -
Sensitive value committed in
*.auto.tfvars. The file lands in Git; the secret leaks to every contributor and the Git history. -
CI logs the
-var-filecontent. The file is mounted from a secret, the CI step dumps the working directory, and the secret appears in the build log.
Recovery
When the wrong value lands in a plan, the recovery is the same in every case: identify the source, override it, and re-plan.
# READ-ONLY — inspect the resolved value
echo "region=${var.region}"
# READ-ONLY — list every source that could set the value
terraform plan -var-file=production.tfvars | grep -i "var.region"
If a TF_VAR_* is set unexpectedly:
# READ-ONLY
env | grep ^TF_VAR_
# RESET
unset TF_VAR_region
If the issue is a stray *.auto.tfvars:
# READ-ONLY
ls -1 *.auto.tfvars terraform.tfvars
Production guidance
- One source per environment. A single
-var-file=environments/prod.tfvarsin CI; no CLI overrides except documented emergencies. - Stable filenames. If you use auto-loaded files, the filename must not change between environments.
- Explicit
TF_VAR_*policy. CI runsunset TF_VAR_*before the apply to neutralise leakage. - Audit
.tfvarsfor secrets. Usegit-secretsor a pre-commit hook to block commits that contain credential-shaped strings. - Never commit
*.auto.tfvars.jsonwith sensitive values. Usedatasources for secrets, not tfvars.
What comes next
The next lesson is Sensitive Values and the Production Interface —
what sensitive = true actually hides, what it does not, and the audit
discipline that turns the flag into a real control.
Verification
- You declare
default = "eu-west-2"and pass-var-file=prod.tfvarswhich setsregion = "us-east-1". What does Terraform use? - Two files exist:
alpha.auto.tfvarsandbeta.auto.tfvars. Both setregion. Which wins, and why? - CI runs
terraform planwith no flags. Your local checkout hasprod.auto.tfvarsbut the CI checkout does not. What is the difference in resolved values, and what is the production risk? TF_VAR_db_passwordis exported in your shell but not in CI. What is the most likely production outcome and how do you prevent it?
Knowledge check · 7 questions
Q1. Which input source has the highest precedence for variable values?
Q2. Files matching `*.auto.tfvars.json` are loaded automatically in alphabetical order.
Q3. Both `terraform.tfvars` and `production.auto.tfvars` define the same variable. Which value wins?
Q4. Which of the following sources override the `default` in a variable block? (Select all that apply.)
Q5. Where should sensitive values such as database passwords come from in production?
Q6. What is the safest way to pass per-environment values into a Terraform CI pipeline?
Q7. Engineer A runs `terraform plan` locally and sees region=us-east-1. CI runs the same configuration and plans region=eu-west-2. The same `-var-file` is passed in both. What is the most likely cause?
Passing score: 75%. Answers are checked in this browser.