File & Environment #
Declaring variables is only half the work. The other half is the strategy for how those variable values are managed β in which files, by whom, how sensitive ones are secured, and how to make sure every environment gets the right values without excessive configuration duplication. Without a clear strategy, a multi-environment project quickly becomes a confusing pile of tfvars files.
flowchart TD
A["π Default value\nin the variable declaration"] -->|priority rises| B["π terraform.tfvars"]
B -->|priority rises| C["π *.auto.tfvars\nread automatically"]
C -->|priority rises| D["π§ TF_VAR_*\nenvironment variables"]
D -->|priority rises| E["β‘ -var-file flag"]
E -->|highest priority| F["β‘ -var flag"]
style A fill:#6b7280,stroke:#4b5563,color:#fff
style B fill:#3b82f6,stroke:#1e40af,color:#fff
style C fill:#3b82f6,stroke:#1e40af,color:#fff
style D fill:#f59e0b,stroke:#d97706,color:#fff
style E fill:#f97316,stroke:#ea580c,color:#fff
style F fill:#ef4444,stroke:#dc2626,color:#fffThe tfvars File Hierarchy #
Terraform reads variable files in a specific order. Understanding this hierarchy prevents confusion about which value ends up being used.
VARIABLE FILE READING ORDER (from lowest to highest):
1. default in the variable declaration
2. terraform.tfvars (auto-loaded, if present)
3. terraform.tfvars.json (auto-loaded, if present)
4. *.auto.tfvars (auto-loaded, sorted alphabetically)
5. *.auto.tfvars.json (auto-loaded, sorted alphabetically)
6. -var-file="file.tfvars" (via flag, flag order determines priority)
7. -var="key=value" (via flag, highest priority)
Values read later OVERWRITE earlier ones.
flowchart LR
A["terraform.tfvars\nπ Always read"] --> C["Terraform\nEngine"]
B["production.auto.tfvars\nπ Read automatically\nin alphabetical order"] --> C
D["custom.tfvars\nβ‘ Must be flagged\n-var-file"] --> C
style A fill:#3b82f6,stroke:#1e40af,color:#fff
style B fill:#10b981,stroke:#059669,color:#fff
style C fill:#f59e0b,stroke:#d97706,color:#fff
style D fill:#8b5cf6,stroke:#6d28d9,color:#ffftfvars File Structure for Multi-Environment #
The cleanest pattern for multi-environment is one file per environment, selected at apply time with the -var-file flag.
environments/
βββ dev.tfvars
βββ staging.tfvars
βββ production.tfvars
# environments/dev.tfvars
environment = "dev"
instance_type = "t3.micro"
instance_count = 1
database_config = {
instance_class = "db.t3.micro"
allocated_storage = 20
multi_az = false
backup_retention = 1
}
tags = {
Environment = "dev"
CostCenter = "engineering"
ManagedBy = "terraform"
}
# environments/production.tfvars
environment = "production"
instance_type = "t3.medium"
instance_count = 3
database_config = {
instance_class = "db.r5.large"
allocated_storage = 500
multi_az = true
backup_retention = 30
}
tags = {
Environment = "production"
CostCenter = "engineering"
ManagedBy = "terraform"
}
# Use at apply time
terraform apply -var-file="environments/dev.tfvars"
terraform apply -var-file="environments/production.tfvars"
The terraform.tfvars File for Team Defaults #
terraform.tfvars is read automatically and is suitable for values that apply across all environments β defaults that don’t need to be overridden per environment.
# terraform.tfvars β default values, committed to Git
# (don't store secrets here)
region = "ap-southeast-1"
owner = "platform-team"
common_tags = {
Project = "my-app"
ManagedBy = "terraform"
Repository = "github.com/org/infrastructure"
}
# Values that are the same in every environment
enable_monitoring = true
log_retention_days = 30
The TF_VAR_* Environment Variable #
Environment variables with the TF_VAR_ prefix are the right way to pass values that differ per context β especially in CI/CD pipelines and for secrets.
# Terraform reads TF_VAR_<variable_name> automatically
# The variable name must be an EXACT MATCH (case-sensitive)
export TF_VAR_environment="production"
export TF_VAR_instance_type="t3.medium"
# For variables with complex types, use the HCL/JSON format
export TF_VAR_database_config='{"instance_class":"db.r5.large","allocated_storage":500,"multi_az":true}'
terraform apply
# Terraform reads TF_VAR_* and uses them as variable values
# Usage example in GitHub Actions CI/CD
name: Terraform Apply Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
- name: Terraform Apply
env:
# Non-sensitive values from the tfvars file
TF_VAR_environment: "production"
# Secrets from GitHub Secrets β never enter the code
TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
TF_VAR_api_key: ${{ secrets.API_KEY }}
# AWS credentials
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: terraform apply -auto-approve -var-file="environments/production.tfvars"
Separating Secrets from Regular Configuration #
Secrets must never be committed to Git, even in .tfvars files. There are two main approaches.
# APPROACH 1: Read secrets from AWS Secrets Manager via a data source
# The secret is managed outside Terraform; Terraform only reads it
data "aws_secretsmanager_secret_version" "db_credentials" {
secret_id = "production/database/credentials"
}
locals {
db_credentials = jsondecode(
data.aws_secretsmanager_secret_version.db_credentials.secret_string
)
}
resource "aws_db_instance" "main" {
username = local.db_credentials.username
password = local.db_credentials.password
# ... other attributes
}
# APPROACH 2: Sensitive variables filled via TF_VAR_* environment variables
# The secret is managed in the CI/CD secrets manager (GitHub Secrets, HashiCorp Vault)
# and passed to Terraform as an environment variable
variable "db_password" {
type = string
sensitive = true
# No default β Terraform will error if TF_VAR_db_password isn't set
}
resource "aws_db_instance" "main" {
password = var.db_password
}
Anti-Patterns to Avoid #
# ANTI-PATTERN 1: Secrets in .tfvars files committed to Git
# production.tfvars
db_password = "super-secret-password" # β Leaks to everyone with repo access
api_key = "Β«redacted:sk-β¦Β»" # β Stored in Git history forever
# ANTI-PATTERN 2: One tfvars file for all environments
# all-environments.tfvars
dev_instance_type = "t3.micro" # β The file becomes big and confusing
prod_instance_type = "t3.medium"
dev_db_class = "db.t3.micro"
prod_db_class = "db.r5.large"
# Better: a separate file per environment
# ANTI-PATTERN 3: Hardcoded values in the configuration that should be variables
resource "aws_instance" "web" {
instance_type = "t3.micro" # β Hardcoded β can't differ per environment
}
# Better: instance_type = var.instance_type
Using terraform.tfvars.json
#
Besides the HCL format, Terraform also supports the JSON format for variable files.
// terraform.tfvars.json
{
"environment": "production",
"instance_type": "t3.medium",
"instance_count": 3,
"tags": {
"Project": "MyApp",
"ManagedBy": "Terraform"
}
}
# terraform.tfvars.json is read automatically, same as terraform.tfvars
terraform plan
# The loading order for JSON is the same as HCL:
# 1. terraform.tfvars.json (if present, together with terraform.tfvars)
# 2. *.auto.tfvars.json (alphabetical)
# 3. -var-file flag
Environment-Specific Variable Files #
# Common pattern: one tfvars file per environment
# environments/
# βββ dev.tfvars
# βββ staging.tfvars
# βββ production.tfvars
terraform plan -var-file="environments/production.tfvars"
terraform apply -var-file="environments/staging.tfvars"
# In CI/CD, pick the file based on the branch or a parameter
if [ "$ENVIRONMENT" == "production" ]; then
terraform apply -var-file="environments/production.tfvars" -auto-approve
elif [ "$ENVIRONMENT" == "staging" ]; then
terraform apply -var-file="environments/staging.tfvars" -auto-approve
fi
Variable Precedence Deep Dive #
Terraform has a specific priority order for determining variable values.
VARIABLE PRIORITY (from low to high):
1. Default value in the variable declaration
variable "x" { default = "low" }
2. terraform.tfvars (and terraform.tfvars.json)
x = "medium-low"
3. *.auto.tfvars (and *.auto.tfvars.json) β alphabetical
config.auto.tfvars: x = "medium"
4. -var-file flag
terraform plan -var-file="custom.tfvars"
x = "medium-high"
5. -var flag and TF_VAR_ environment variables
terraform plan -var="x=high"
TF_VAR_x="high" terraform plan
6. Input at the prompt (if there's no default)
Terraform will ask for interactive input
# Example: override in CI/CD
# Default: terraform.tfvars β environment = "dev"
# CI/CD override:
terraform plan -var="environment=staging"
# This overrides the value in terraform.tfvars
# Environment variable override:
export TF_VAR_instance_type="t3.large"
terraform plan
# TF_VAR_instance_type overrides all other sources
tfvars Security #
# DON'T commit tfvars files containing secrets to Git!
# .gitignore:
*.tfvars
*.tfvars.json
!terraform.tfvars.example
# For shared (non-sensitive) values:
# Commit terraform.tfvars.example with placeholders
# terraform.tfvars.example (commit this)
# environment = "dev"
# instance_type = "t3.micro"
# db_password = "CHANGE_ME"
# terraform.tfvars (DON'T commit)
environment = "production"
instance_type = "m5.large"
db_password = "actually-secret-password"
# Alternative: use environment variables for secrets
# export TF_VAR_db_password="secret"
# Terraform will read from the TF_VAR_ prefix
Hierarchical Variable Loading #
TERRAFORM VARIABLE LOADING ORDER:
1. Environment variables (TF_VAR_*)
βββ Loaded automatically from the shell
2. terraform.tfvars
βββ Auto-loaded if present in the working directory
3. *.auto.tfvars
βββ Auto-loaded, alphabetical order
4. -var-file=explicit.tfvars
βββ Explicit, can specify multiple
5. -var="key=value"
βββ Command line, highest priority
NAMING CONVENTION:
common.tfvars β Shared values across all envs
dev.tfvars β Dev-specific values
staging.tfvars β Staging-specific values
production.tfvars β Production-specific values
secrets.tfvars β NEVER commit (in .gitignore)
# Combine multiple var files
terraform apply -var-file="common.tfvars" -var-file="environments/dev.tfvars" -var-file="regions/ap-southeast-1.tfvars"
Summary #
- One tfvars file per environment (
dev.tfvars,staging.tfvars,production.tfvars) selected with-var-fileis the cleanest multi-environment pattern.terraform.tfvarsfor team defaults that apply across all environments β committed to Git, no secrets.TF_VAR_*environment variables for values that differ per context in CI/CD and for all secrets.- Secrets never go into tfvars files committed to Git β use
TF_VAR_*from a secrets manager or read directly from AWS Secrets Manager via a data source.- Priority hierarchy: CLI flags > tfvars files > auto-loaded tfvars > env vars > defaults β more specific values always win.
- Sensitive variables without defaults force values to always be provided explicitly β Terraform will error if there’s no value, preventing forgotten secrets.
β Previous: Type & Validation Β Next: What is an Output? β