# terraform / variables / configuration
Write Once, Deploy Everywhere: Input Variables
Input variables make your Terraform code dynamic, reusable, and configurable. Instead of hardcoding an instance type into main.tf and editing it for every environment, you declare a placeholder once and feed it different values from outside the configuration.
Without Variables, You Keep Editing Code
Say your company has three environments — Development, Testing, Production — and each needs a different EC2 instance type. Hardcode the value and main.tf has to change every single time you deploy somewhere new.
| Environment | EC2 Instance |
|---|---|
| Development | t2.micro |
| Testing | t2.medium |
| Production | m5.large |
resource "aws_instance" "web" { ami = "ami-0123456789abcdef" instance_type = "t2.micro" } // Testing: change to "t2.medium" // Production: change to "m5.large" // You keep editing this file every time.
The Three-File Model
Instead of changing main.tf, keep it the same and only change the values. A typical project splits into three files, each with exactly one job.
variables.tf
Tells Terraform "my project needs these values." It's an empty placeholder — no value lives here, just a name and a type. Someone will provide the value later.
terraform.tfvars
Supplies the actual value for each declared variable. This is the file that changes between Development, Testing, and Production.
main.tf
References the variable with var.instance_type instead of a literal string. This file never has to change again.
Reading Order
instance_typevariable "instance_type" { type = string }
instance_type = "t2.medium"
resource "aws_instance" "web" { ami = "ami-0123456789abcdef" instance_type = var.instance_type }
Variable Types
String — instance_type
Stores text. The most common variable type — instance types, regions, key pairs, AMI IDs are all strings.
variable "instance_type" { type = string }
instance_type = "t2.micro"
instance_type = var.instance_type
Number — disk_size
Stores numeric values — EBS volume sizes, auto-scaling desired capacity, port numbers.
variable "disk_size" { type = number }
disk_size = 100
size = var.disk_size // → 100 GB EBS volume
Bool — monitoring
Stores true or false — feature toggles like detailed monitoring, encryption, or public access.
variable "monitoring" { type = bool }
monitoring = true
monitoring = var.monitoring // → Detailed Monitoring: Enabled
List — subnets
Stores an ordered collection of values of the same type, accessed by index.
variable "subnets" { type = list(string) }
subnets = [ "subnet-111", "subnet-222", "subnet-333" ]
subnet_id = var.subnets[0] // → launched in subnet-111
Map — tags
Stores key-value pairs, all values sharing the same type — the classic shape for resource tags.
variable "tags" { type = map(string) }
tags = { Name = "WebServer" Environment = "Development" Owner = "DevOps" }
tags = var.tags
Object — server
Bundles several related values of different types into a single structured variable.
variable "server" { type = object({ instance_type = string disk = number monitoring = bool }) }
server = { instance_type = "t3.large" disk = 200 monitoring = true }
instance_type = var.server.instance_type monitoring = var.server.monitoring
Default Values & Validation
Skip terraform.tfvars Entirely
When almost every server uses the same region, give the variable a default right in variables.tf. There's no need to repeat it in terraform.tfvars — Terraform falls back to the default automatically.
variable "region" { default = "ap-south-1" }
Enforcing Company Policy
Say policy only allows t2.micro or t2.medium. A validation block rejects anything else with a clear error message before Terraform ever calls the provider.
variable "instance_type" { validation { condition = contains( ["t2.micro","t2.medium"], var.instance_type ) error_message = "Only t2.micro or t2.medium allowed." } }
instance_type = "t2.micro" → Terraform Apply Successful.
instance_type = "m5.large" → Error: Only t2.micro or t2.medium allowed.
Loading Values
Terraform loads this file automatically on every apply — no flag needed.
region = "ap-south-1" instance_type = "t2.micro" disk_size = 100
Any file ending in .auto.tfvars — like dev.auto.tfvars, qa.auto.tfvars, prod.auto.tfvars — loads automatically too, no -var-file flag required.
instance_type = "t2.micro"
CI/CD pipelines (Jenkins, GitHub Actions, GitLab, Azure DevOps) inject values as environment variables instead of files.
Complete Flow
Easy Way to Remember
| File | Why | Example |
|---|---|---|
variables.tf | Declares what the project needs | instance_type, region |
terraform.tfvars | Provides the actual values | instance_type = "t2.medium" |
main.tf | Uses values to create infrastructure | instance_type = var.instance_type |
TF_VAR_* | Passes values from CI/CD or terminal | export TF_VAR_instance_type="m5.large" |
The code in main.tf is written once. Dev, QA, and Production all run the same code — only the input values change, through terraform.tfvars, .auto.tfvars, or TF_VAR_ environment variables.