How do you use tfvars files to pass environment-specific values across Dev, QA, UAT, and Prod without duplicating Terraform code?
Quick Answer
Create a single set of Terraform modules and root configurations, then use per-environment .tfvars files (dev.tfvars, qa.tfvars, uat.tfvars, prod.tfvars) to inject environment-specific values like instance sizes, replica counts, and CIDR blocks. The terraform plan -var-file flag selects the correct file, keeping code DRY while allowing each environment to diverge on sizing and configuration.
Detailed Answer
Using tfvars files for multi-environment management is like having a single restaurant menu (Terraform code) with portion size options (tfvars): the recipe stays the same whether you order a small (dev), medium (qa), large (uat), or extra-large (prod) — only the quantities change. This pattern eliminates code duplication while giving each environment its own tunable parameters.
The variable definition layer starts in variables.tf, where you declare every configurable parameter with a type constraint, description, and optionally a default value. Variables without defaults are mandatory and must be provided by the tfvars file. Use object types for related parameters: instead of separate variables for each RDS setting, create a variable of type object({ instance_class = string, allocated_storage = number, multi_az = bool, backup_retention_days = number }) called database_config. This groups related values and makes tfvars files self-documenting.
The tfvars files live in an environments/ directory: environments/dev.tfvars, environments/qa.tfvars, environments/uat.tfvars, environments/prod.tfvars. Each file sets the same variables with environment-appropriate values. Dev might use db.t3.medium with 20GB storage and single-AZ, while Prod uses db.r6g.2xlarge with 500GB and multi-AZ enabled. The key principle is that tfvars files contain only values, never logic. Conditional logic belongs in the Terraform code itself using ternary expressions or lookup maps.
The CI/CD pipeline selects the correct tfvars file based on the target environment. A typical command is terraform plan -var-file=environments/prod.tfvars -out=prod.tfplan. The pipeline determines the environment from the Git branch (feature/* uses dev.tfvars, release/* uses uat.tfvars, main uses prod.tfvars) or from a pipeline variable. This pattern integrates cleanly with the approval workflow: the plan output shows exactly what will change in the target environment before anyone approves.
For complex configurations, layer multiple tfvars files. Use a common.tfvars for values shared across all environments (organization name, DNS domain, tagging standards), then overlay environment-specific files: terraform plan -var-file=common.tfvars -var-file=environments/prod.tfvars. Later files override values from earlier ones, so environment-specific settings take precedence over common defaults.
The critical gotcha is tfvars file drift. Over time, someone adds a new variable to dev.tfvars but forgets to add it to prod.tfvars. Without a default value, the prod plan fails. Prevent this by treating tfvars files as a matrix: every variable declared without a default must appear in every tfvars file. Enforce this with a CI check that parses variables.tf for required variables and validates their presence in all tfvars files. Another gotcha is accidentally committing sensitive values (database passwords, API keys) in tfvars files. Sensitive values should come from environment variables (TF_VAR_*), HashiCorp Vault, or AWS Secrets Manager data sources — never from tfvars files in version control.