Skip to main content

Input Variable

A Terraform input variable is a parameter that makes a module or configuration reusable by letting callers pass in different values at runtime. Variables are declared with a variable block and can have a type, default value, description, and validation rules.

What Is a Terraform Input Variable?

A Terraform input variable is the way you parameterise your configuration. Instead of hardcoding "ap-south-1" in ten places, you declare it once as variable "aws_region" and reference it everywhere as var.aws_region. When you want to change the region, you change one value — not ten.

Think of variables as the blanks in a template. Your Terraform configuration is the template — write it once. The .tfvars file fills in the blanks differently for dev, staging, and production.

At Zerodha, the same Terraform configuration provisions the payments API infrastructure in three environments. The only difference between environments is the variable values: smaller instances in dev, larger in prod, deletion protection off in dev, on in prod. Variables make this zero-duplication.

◈ DIAGRAM
+------------------------------------------+
| dev.tfvars |
| environment = "dev" |
| instance_type = "t3.micro" |
+------------------------------------------+
|
+------------------------------------------+
| prod.tfvars |
| environment = "prod" |
| instance_type = "t3.large" |
+------------------------------------------+
|
v
+------------------------------------------+
| variable "environment" { type = string } |
| variable "instance_type" { type = string}|
+------------------------------------------+
|
v
+------------------------------------------+
| resource "aws_instance" "app" { |
| instance_type = var.instance_type |
| } |
+------------------------------------------+

Declaring Variables

Every variable must be declared in a variable block before it can be used:

HCL
# variables.tf
# Minimum declaration — just a name and type
variable "aws_region" {
type = string
}
# Full declaration — best practice
variable "environment" {
description = "Deployment environment — controls naming, sizing, and protection"
type = string
default = "dev" # used if no value is provided by the caller
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Must be dev, staging, or prod."
}
}

Variable Types

HCL
# Primitive types
variable "region" { type = string } # "ap-south-1"
variable "port" { type = number } # 8080
variable "enable_logs" { type = bool } # true or false
# Collection types
variable "azs" {
type = list(string)
default = ["ap-south-1a", "ap-south-1b"]
}
variable "instance_types" {
type = map(string)
default = {
dev = "t3.micro"
prod = "t3.large"
}
}
variable "allowed_ports" {
type = set(number) # set = no duplicates
default = [80, 443, 8080]
}
# Structural types — named attributes with different types
variable "database" {
type = object({
engine = string
instance_class = string
storage_gb = number
multi_az = bool
})
default = {
engine = "postgres"
instance_class = "db.t3.medium"
storage_gb = 20
multi_az = false
}
}
# List of objects — for dynamic blocks
variable "ingress_rules" {
type = list(object({
port = number
description = string
cidr_blocks = list(string)
}))
default = []
}
# any — Terraform infers the type from the value
variable "flexible" {
type = any
}

Variable Validation

HCL
variable "project_name" {
type = string
# Multiple validation blocks are allowed
validation {
condition = can(regex("^[a-z0-9-]+$", var.project_name))
error_message = "Project name: only lowercase letters, numbers, hyphens."
}
validation {
condition = length(var.project_name) <= 20
error_message = "Project name must be 20 characters or fewer."
}
}
variable "vpc_cidr" {
type = string
validation {
# can() returns true if the expression succeeds without error
condition = can(cidrnetmask(var.vpc_cidr))
error_message = "Must be a valid CIDR block (e.g., 10.0.0.0/16)."
}
}

Sensitive Variables

HCL
variable "db_password" {
description = "Database master password — pass via TF_VAR_db_password env var"
type = string
sensitive = true # value is hidden in plan output and logs
# IMPORTANT: sensitive = true hides the value in terminal output
# It does NOT prevent the value being stored in the state file
# Always use an encrypted remote backend with restricted access
}

Referencing Variables

HCL
resource "aws_instance" "app" {
# Reference with var.<name>
instance_type = var.instance_type
# Access list item
availability_zone = var.azs[0]
# Access map value
instance_type = var.instance_types[var.environment]
# Access object attribute
tags = { Engine = var.database.engine }
}

Passing Values to Variables

Bash
# 1. Default value in variable block — used if nothing else provided
# 2. terraform.tfvars (auto-loaded if it exists)
# 3. *.auto.tfvars (auto-loaded, alphabetical order)
# 4. -var-file flag
terraform plan -var-file=prod.tfvars
# 5. TF_VAR_ environment variables
export TF_VAR_db_password="S3cr3tP@ss"
export TF_VAR_environment="prod"
# 6. -var flag — highest priority, overrides everything
terraform plan -var="environment=prod" -var="instance_type=t3.large"

Troubleshooting Variables

Error Root Cause Fix
Error: No value for required variable Variable has no default and no value was provided Add a default or pass a value via tfvars or -var
Error: Invalid value for variable Validation condition returned false Read the error_message in the validation block
Error: Variables not allowed Using var.x inside a backend block Backend blocks cannot use variables — hardcode or use partial config
Error: Unsupported attribute "x" Wrong key name in object type Check the object type definition
Value shows (sensitive value) in plan Variable has sensitive = true Expected — use terraform output -raw to read sensitive outputs
Remember

Variable declarations in variable {} blocks are like function signatures — they define what can be passed. Values in .tfvars files or TF_VAR_ env vars are like function arguments — they provide the actual data.

Common Mistake

Putting database passwords in terraform.tfvars and committing it to Git. Anyone with repo access then has the production database password. Use TF_VAR_ environment variables for all secrets, or read them from AWS Secrets Manager at apply time.

Frequently Asked Questions

What's the practical benefit of declaring type and validation on a Terraform input variable versus leaving it untyped?

An untyped variable accepts anything, so a typo like passing a string where a list was expected only surfaces as a confusing downstream error deep in resource creation. Declaring `type = string` or a structured object type makes Terraform reject bad input at plan time with a clear message, and `validation` blocks let you enforce domain rules (e.g., instance type must match a regex) before any API call is made, catching mistakes minutes earlier in the workflow.

What's a common mistake when using default values on Terraform variables?

Setting a 'safe-looking' default (like a small instance size or a specific region) on a variable meant to be explicitly supplied per environment — it silently works in dev, then someone forgets to override it and it ships to production unnoticed. For anything environment-critical (region, environment name, sizing), it's often safer to omit the default entirely so Terraform forces the caller to supply a value rather than falling back silently.