Terraform & IaC

Write Once, Deploy Everywhere: Input Variables

Lesson 5 of 14 · 12 min · Advanced

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.

EnvironmentEC2 Instance
Developmentt2.micro
Testingt2.medium
Productionm5.large
main.tf
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

  • variables.tf → declares instance_type
  • ↓
  • terraform.tfvars → instance_type = "t2.medium"
  • ↓
  • main.tf → instance_type = var.instance_type
  • ↓ Terraform substitutes the value
  • instance_type = "t2.medium"
variables.tf
variable "instance_type" {
  type = string
}
terraform.tfvars
instance_type = "t2.medium"
main.tf
resource "aws_instance" "web" {
  ami           = "ami-0123456789abcdef"
  instance_type = var.instance_type
}
terminal
terminal$ terraform apply

Terraform will perform the following actions:
+ aws_instance.web
    instance_type = "t2.medium"
Plan: 1 to add.
Apply complete!

Variable Types

String — instance_type

Stores text. The most common variable type — instance types, regions, key pairs, AMI IDs are all strings.

variables.tf
variable "instance_type" {
  type = string
}
terraform.tfvars
instance_type = "t2.micro"
main.tf
instance_type = var.instance_type

Number — disk_size

Stores numeric values — EBS volume sizes, auto-scaling desired capacity, port numbers.

variables.tf
variable "disk_size" {
  type = number
}
terraform.tfvars
disk_size = 100
main.tf
size = var.disk_size
// → 100 GB EBS volume

Bool — monitoring

Stores true or false — feature toggles like detailed monitoring, encryption, or public access.

variables.tf
variable "monitoring" {
  type = bool
}
terraform.tfvars
monitoring = true
main.tf
monitoring = var.monitoring
// → Detailed Monitoring: Enabled

List — subnets

Stores an ordered collection of values of the same type, accessed by index.

variables.tf
variable "subnets" {
  type = list(string)
}
terraform.tfvars
subnets = [
  "subnet-111",
  "subnet-222",
  "subnet-333"
]
main.tf
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.

variables.tf
variable "tags" {
  type = map(string)
}
terraform.tfvars
tags = {
  Name = "WebServer"
  Environment = "Development"
  Owner = "DevOps"
}
main.tf
tags = var.tags

Object — server

Bundles several related values of different types into a single structured variable.

variables.tf
variable "server" {
  type = object({
    instance_type = string
    disk = number
    monitoring = bool
  })
}
terraform.tfvars
server = {
  instance_type = "t3.large"
  disk = 200
  monitoring = true
}
main.tf
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.

variables.tf
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.

variables.tf
variable "instance_type" {
  validation {
    condition = contains(
      ["t2.micro","t2.medium"],
      var.instance_type
    )
    error_message = "Only t2.micro or t2.medium allowed."
  }
}

Loading Values

terraform.tfvars

Terraform loads this file automatically on every apply — no flag needed.

region = "ap-south-1"
instance_type = "t2.micro"
disk_size = 100

*.auto.tfvars

Any file ending in .auto.tfvars — like dev.auto.tfvars, qa.auto.tfvars, prod.auto.tfvars — loads automatically too, no -var-file flag required.

dev.auto.tfvars
instance_type = "t2.micro"

TF_VAR_*

CI/CD pipelines (Jenkins, GitHub Actions, GitLab, Azure DevOps) inject values as environment variables instead of files.

$ 
$ 

Complete Flow

  • variables.tf → declares instance_type
  • ↓
  • terraform.tfvars → instance_type = "t2.medium"
  • ↓
  • main.tf → instance_type = var.instance_type
  • ↓ Terraform replaces
  • instance_type = "t2.medium"
  • ↓
  • AWS API
  • ↓
  • Creates EC2 · Instance Type = t2.medium

Easy Way to Remember

FileWhyExample
variables.tfDeclares what the project needsinstance_type, region
terraform.tfvarsProvides the actual valuesinstance_type = "t2.medium"
main.tfUses values to create infrastructureinstance_type = var.instance_type
TF_VAR_*Passes values from CI/CD or terminalexport 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.