In many Terraform examples, values are hardcoded directly inside resource blocks. That works for learning, but it’s not flexible for real projects because you end up coupling infrastructure code with environment-specific strings and numbers.

Terraform, like other Infrastructure-as-Code tools, solves this using input variables. Variables let you reuse the same configuration across environments (dev/test/prod), avoid copy-paste, validate inputs using types, and keep sensitive values out of committed files.
Defining variables
A variable is declared using a variable block:
variable "variable_name" {
# optional arguments go here
}
Convention: Most projects keep variable declarations in a file named
variables.tf. Terraform loads all*.tffiles in the directory, so the filename is not mandatory—butvariables.tfis the common standard.
1) Simple variable block (no default)
variable "env" {
}
Reference it inside resources using var.<name>:
resource "null_resource" "example" {
triggers = {
env = var.env
}
}
If a variable has no default, Terraform will prompt you for a value during interactive runs.
When to use
- Learning locally
- Quick experiments / POCs
Avoid in CI/CD
- Prompts are not desirable in pipelines; instead, pass values via files, CLI flags, or environment variables.
2) Variable block with a default value
variable "greeting" {
default = "Hello world"
}
If a default exists, Terraform won’t prompt for that variable. Defaults are useful as safe fallbacks, and you can still override them using .tfvars, -var, or environment variables.
3) Variable block with type
Adding a type makes your configuration safer and more self-documenting:
variable "instance_count" {
type = number
default = 2
}
Common Terraform variable types
string,number,boollist(TYPE),set(TYPE),map(TYPE)object({ ... }),tuple([ ... ])any(Terraform tries to infer a single concrete type)nullis a special “absent/omitted” value (not a type)
4) Variable block with type and description
Descriptions help future maintainers understand the intent:
variable "greeting" {
type = string
default = "Hello world"
description = "A greeting used by this module"
}
Passing variable values
Terraform supports multiple ways to assign values to root module variables. Below are the most common approaches used in real projects.
1) Pass individual values in the CLI (-var)
terraform plan -var="env=dev" -var="instance_count=2"
terraform apply -var="region=centralindia"
When to use
- Quick overrides while learning
- Temporary one-off changes
- Small CI overrides (only a few variables)
Notes / pitfalls
- Terraform parses these values and validates them against the variable type.
- For complex values, quoting becomes painful; prefer
.tfvarsfiles for readability. - Avoid passing secrets here because shell history may capture them.
2) Use a variable definition file (.tfvars)
A .tfvars file contains only variable values (no variable {} blocks). This is the most readable approach for real projects.
Example: dev.tfvars
env = "dev"
region = "centralindia"
instance_count = 2
tags = {
owner = "siva"
app = "jyotishlabs"
}
Use it like this:
terraform plan -var-file="dev.tfvars"
terraform apply -var-file="dev.tfvars"
When to use
- Different environments (dev/test/prod)
- Larger sets of values (cleaner than CLI)
- Sharing non-secret config with the team
Notes
.tfvarscontains only values, notvariable {}blocks.- Keep secrets out of committed
.tfvarsfiles (use env vars or a secrets manager).
3) Explicit environment files (-var-file)
This is the most common real-world workflow: store environment values in dedicated files and pass them explicitly.
Example structure
infra/
main.tf
variables.tf
environments/
dev.tfvars
prod.tfvars
Run:
terraform apply -var-file="environments/dev.tfvars"
Why this is good
- Very clear which environment values are being used
- Works well in CI/CD pipelines
- Easy to layer configs (later files override earlier ones):
terraform apply \
-var-file="environments/common.tfvars" \
-var-file="environments/dev.tfvars"
4) Auto-loading variable files (no flags needed)
Terraform automatically loads variable files if you name them in certain ways:
terraform.tfvarsterraform.tfvars.json*.auto.tfvars*.auto.tfvars.json
Example
- If you create
terraform.tfvarsordev.auto.tfvars, then you can run:
terraform plan
terraform apply
When to use
- Single-environment projects
- When you want “zero CLI arguments”
- Local development convenience
Caution
- Auto-loading can be confusing in teams if multiple
.auto.tfvarsfiles exist. - In CI/CD, explicit
-var-fileis usually safer and more predictable.
5) Environment variables (TF_VAR_…)
Terraform can read environment variables that start with TF_VAR_.
Example (Linux/macOS)
export TF_VAR_env="dev"
export TF_VAR_instance_count=2
terraform plan
Example (PowerShell)
$env:TF_VAR_env="dev"
$env:TF_VAR_instance_count="2"
terraform plan
When to use
- Sensitive values (tokens, passwords) you don’t want in files
- CI/CD pipelines (secrets injected securely)
- Temporary overrides without changing repo files
Notes / pitfalls
- They must match the variable name exactly:
variable "region"→TF_VAR_region - Complex types (map/object) are harder to pass via env vars; keep it simple or use files/secret stores.
Precedence: which value wins?
If Terraform receives the same variable from multiple sources, it uses this precedence (highest first):
-varand-var-file*.auto.tfvars/*.auto.tfvars.json(lexical order)terraform.tfvars.jsonterraform.tfvars- Environment variables (
TF_VAR_...) defaultin thevariableblock
A word of caution on secrets
Do not commit sensitive values into version control. Prefer environment variables injected by CI/CD, a secrets manager (such as Vault or a cloud secrets service), or encrypted secret workflows.
Also remember: removing a secret from Git later doesn’t erase it from history unless you rewrite history and rotate the credential. Treat leaked secrets as compromised and rotate them immediately.



