tfvars & Variable Files
Declaring input variables tells Terraform what a configuration accepts; variable files and the -var flag tell it what values to use. Hard-coding values defeats the purpose of variables, so Terraform offers several mechanisms to supply them—from auto-loaded terraform.tfvars files to command-line overrides. Understanding the precedence order between these sources is essential for managing multiple environments cleanly and avoiding surprising configuration drift. Everything described here works identically in OpenTofu.
The terraform.tfvars file
The simplest way to assign values is a file named exactly terraform.tfvars (or terraform.tfvars.json for JSON). Terraform loads it automatically on every plan and apply—you never pass it explicitly. Given these declarations:
variable "region" {
type = string
default = "us-east-1"
}
variable "instance_type" {
type = string
}
variable "tags" {
type = map(string)
default = {}
}
You assign values in terraform.tfvars using plain name = value syntax (this is not a variable block):
region = "us-west-2"
instance_type = "t3.micro"
tags = {
Environment = "production"
Team = "platform"
}
Run terraform plan and the values are picked up without any flags:
terraform plan
Output:
Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ instance_type = "t3.micro"
+ tags = {
+ "Environment" = "production"
+ "Team" = "platform"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
Auto-loaded *.auto.tfvars files
Any file matching *.auto.tfvars or *.auto.tfvars.json is also loaded automatically, in addition to terraform.tfvars. This lets you split values across multiple files by concern—for example network.auto.tfvars and tags.auto.tfvars. When multiple auto files are present they are processed in lexical (alphabetical) order, and later files override earlier ones for the same variable.
# network.auto.tfvars
region = "eu-central-1"
Auto-loaded files are convenient but easy to forget. A stray
*.auto.tfvarsleft in a directory silently changes every run. Keep auto files under version control and review them as carefully as the configuration itself.
The -var-file flag
For values you do not want loaded automatically—most commonly per-environment settings—use -var-file to load a named file explicitly. The file can have any name; .tfvars is the conventional extension.
terraform plan -var-file="prod.tfvars"
You can pass -var-file multiple times; files are applied left to right, so later files win on conflicts. This is the recommended pattern for environment separation:
terraform apply -var-file="common.tfvars" -var-file="staging.tfvars"
The -var flag
For one-off overrides—or values injected by a CI pipeline—pass -var directly on the command line. Each flag sets a single variable. Complex types use HCL literal syntax in quotes.
terraform apply \
-var="instance_type=t3.large" \
-var='tags={Environment="hotfix"}'
-var is the highest-precedence source, which makes it ideal for surgical overrides but dangerous for routine use: command-line values are not version-controlled and are invisible to teammates reviewing the repository.
Precedence order
When the same variable is set in more than one place, Terraform applies sources in a fixed order. Later sources in the list below override earlier ones:
| Order | Source | Loaded |
|---|---|---|
| 1 | Environment variables (TF_VAR_name) | Automatic |
| 2 | terraform.tfvars | Automatic |
| 3 | terraform.tfvars.json | Automatic |
| 4 | *.auto.tfvars / *.auto.tfvars.json (alphabetical) | Automatic |
| 5 | -var and -var-file (in command-line order) | Explicit |
The last value Terraform reads wins. So a -var on the CLI beats a terraform.tfvars entry, which beats a TF_VAR_ environment variable. If a variable has no value from any source and no default, Terraform prompts for it interactively (or fails in -input=false mode used by automation).
Per-environment tfvars
A common layout keeps shared values in the configuration’s defaults and overrides only the differences per environment:
.
├── main.tf
├── variables.tf
├── common.tfvars
└── envs/
├── dev.tfvars
├── staging.tfvars
└── prod.tfvars
# envs/prod.tfvars
region = "us-east-1"
instance_type = "m6i.large"
tags = {
Environment = "prod"
CostCenter = "1042"
}
Select the environment at run time:
terraform apply -var-file="common.tfvars" -var-file="envs/prod.tfvars"
Pair per-environment tfvars with separate state (via backend workspaces or distinct backend keys). A correct
prod.tfvarsapplied against the dev state will happily reconfigure the wrong resources.
Best Practices
- Use
terraform.tfvarsonly for values that are always the same in a given directory; reach for-var-fileto keep environment-specific values explicit and intentional. - Never commit secrets to
.tfvarsfiles—supply sensitive values through environment variables or a secrets manager instead. - Add
*.auto.tfvarsto.gitignoreonly if it holds machine-local overrides; otherwise commit it so runs are reproducible. - Reserve
-varfor CI-injected or one-off values, not routine configuration, since CLI values are not tracked in version control. - Keep one tfvars file per environment under
envs/and require it explicitly, so no run can accidentally fall back to defaults. - Prefer
.tfvars.jsonwhen values are generated by tooling, since JSON is far easier to emit programmatically than HCL.