Chapter 3: Terraform Variables and Locals
Hardcoding values works for demos, not production. Variables make your code flexible. Locals make it DRY (Don’t Repeat Yourself).
Basic Variable Types
Terraform has three basic types: string, number, and bool.
variable "name" {
type = string
description = "User name"
default = "World"
}
variable "counts" {
type = number
default = 5
}
variable "enabled" {
type = bool
default = true
}
Use them:
resource "local_file" "example" {
content = "Hello, ${var.name}! Count: ${var.counts}, Enabled: ${var.enabled}"
filename = "output.txt"
}
count as variable name.Change values:
terraform apply -var="name=Alice" -var="counts=10"

Always add description. Future you will thank you.
Advanced Variable Types
Real infrastructure needs complex data structures.
Lists
Ordered collections of values:
variable "availability_zones" {
type = list(string)
default = ["us-west-2a", "us-west-2b", "us-west-2c"]
}
Access elements:
locals {
first_az = var.availability_zones[0] # "us-west-2a"
all_zones = join(", ", var.availability_zones)
}
Use in resources:
resource "aws_subnet" "public" {
count = length(var.availability_zones)
availability_zone = var.availability_zones[count.index]
# ... other config
}
Maps
Key-value pairs:
variable "instance_types" {
type = map(string)
default = {
dev = "t2.micro"
prod = "t2.large"
}
}
Access values:
resource "aws_instance" "app" {
instance_type = var.instance_types["prod"]
# Or with lookup function
instance_type = lookup(var.instance_types, var.environment, "t2.micro")
}
Objects
Structured data with different types:
variable "database_config" {
type = object({
instance_class = string
allocated_storage = number
multi_az = bool
backup_retention = number
})
default = {
instance_class = "db.t3.micro"
allocated_storage = 20
multi_az = false
backup_retention = 7
}
}
Use in resources:
resource "aws_db_instance" "main" {
instance_class = var.database_config.instance_class
allocated_storage = var.database_config.allocated_storage
multi_az = var.database_config.multi_az
backup_retention_period = var.database_config.backup_retention
}
List of Objects
The power combo - multiple structured items:
variable "servers" {
type = map(object({
size = string
disk = number
}))
default = {
web-1 = { size = "t2.micro", disk = 20 }
web-2 = { size = "t2.small", disk = 30 }
}
}
resource "aws_instance" "servers" {
for_each = var.servers
instance_type = each.value.size
tags = {
Name = each.key
}
root_block_device {
volume_size = each.value.disk
}
}
Sets and Tuples
Set - Like list but unordered and unique:
variable "allowed_ips" {
type = set(string)
default = ["10.0.0.1", "10.0.0.2"]
}
Tuple - Fixed-length list with specific types:
variable "server_config" {
type = tuple([string, number, bool])
default = ["t2.micro", 20, true]
}
Rarely used. Stick with lists and maps for most cases.
Variable Validation
Add rules to validate input:
variable "environment" {
type = string
description = "Environment name"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_count" {
type = number
default = 1
validation {
condition = var.instance_count >= 1 && var.instance_count <= 10
error_message = "Instance count must be between 1 and 10."
}
}
Catches errors before Terraform runs.

Sensitive Variables
Mark secrets as sensitive:
variable "db_password" {
type = string
sensitive = true
}
Won’t appear in logs or plan output. Still stored in state though (encrypt your state!).
Variable Precedence
Multiple ways to set variables. Terraform picks in this order (highest to lowest):
- Command line:
-var="key=value" *.auto.tfvarsfiles (alphabetical order)terraform.tfvarsfile- Environment variables:
TF_VAR_name - Default value in variable block
Setting Variables with Files
Create terraform.tfvars:
environment = "prod"
instance_type = "t2.large"
database_config = {
instance_class = "db.t3.large"
allocated_storage = 100
multi_az = true
backup_retention = 30
}
Run terraform apply - picks up values automatically
Or environment-specific files:
# dev.tfvars
environment = "dev"
instance_type = "t2.micro"
terraform apply -var-file="dev.tfvars"
Locals: Computed Values
Variables are inputs. Locals are calculated values you use internally.
variable "project_name" {
type = string
default = "myapp"
}
variable "environment" {
type = string
default = "dev"
}
locals {
resource_prefix = "${var.project_name}-${var.environment}"
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
}
is_production = var.environment == "prod"
backup_count = local.is_production ? 3 : 1
}
resource "aws_s3_bucket" "data" {
bucket = "${local.resource_prefix}-data"
tags = local.common_tags
}
Use var. for variables, local. for locals.
Outputs
Display values after apply:
output "bucket_name" {
description = "Name of the S3 bucket"
value = aws_s3_bucket.data.id
}
output "is_production" {
value = local.is_production
}
output "db_endpoint" {
value = aws_db_instance.main.endpoint
sensitive = true # Don't show in logs
}
View outputs:
terraform output
terraform output bucket_name
Real-World Example
variable "environment" {
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Must be dev, staging, or prod."
}
}
variable "app_config" {
type = object({
instance_type = string
min_size = number
})
}
locals {
common_tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
# Override for production
min_size = var.environment == "prod" ? 3 : var.app_config.min_size
}
resource "aws_autoscaling_group" "app" {
name = "myapp-${var.environment}-asg"
min_size = local.min_size
desired_capacity = local.min_size
tags = [
for key, value in local.common_tags : {
key = key
value = value
propagate_at_launch = true
}
]
}
Quick Reference
Basic types:
variable "name" { type = string }
variable "count" { type = number }
variable "enabled" { type = bool }
Complex types:
variable "zones" { type = list(string) }
variable "types" { type = map(string) }
variable "config" { type = object({ name = string, size = number }) }
variable "servers" { type = map(object({ size = string, disk = number })) }
Validation:
validation {
condition = contains(["dev", "prod"], var.env)
error_message = "Must be dev or prod."
}
Locals and Outputs:
locals { name = "${var.project}-${var.env}" }
output "result" { value = aws_instance.app.id, sensitive = true }
Variables make your code flexible. Complex types model real infrastructure. Locals keep things DRY. Outputs share information.
With variables and locals in your toolkit, you now know how to make your Terraform code flexible and maintainable. But where does Terraform store the information about what it created? And how does it connect to AWS, Azure, or other cloud providers? That’s what we’ll explore next with state management and providers.