Table of Contents
Introduction
Terraform locals and variables serve different purposes, but beginners often confuse them. This guide clarifies when to use each with practical examples.
Quick Comparison
| Feature | Variables | Locals |
|---|---|---|
| Purpose | Accept external input | Compute internal values |
| Set by | User/environment | Configuration code |
| Scope | Module-wide | Module-wide |
| Override | Yes (tfvars, CLI, env) | No |
| Syntax | var.name | local.name |
Variables - External Input
Variables accept input from outside your module:
variable "environment" {
type = string
default = "dev"
}
variable "instance_count" {
type = number
default = 1
}
Set values via:
terraform apply -var="environment=prod"
Or in terraform.tfvars:
environment = "prod"
instance_count = 3
Locals - Computed Values
Locals are computed values used within your configuration:
locals {
name_prefix = "${var.project}-${var.environment}"
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
}
is_production = var.environment == "prod"
instance_type = local.is_production ? "t3.large" : "t3.micro"
}
When to Use Variables
Use variables when:
- The value comes from outside the module
- Different environments need different values
- Users need to customize behavior
- Modules need input from their callers
variable "region" {
description = "AWS region"
type = string
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
When to Use Locals
Use locals when:
- Computing derived values from variables
- Avoiding repetition (DRY principle)
- Complex expressions used multiple times
- Internal logic that shouldn’t be externally configurable
locals {
# Derived from variables - users shouldn't set these directly
subnet_cidrs = [for i in range(3) : cidrsubnet(var.vpc_cidr, 8, i)]
# Repeated expression - define once, use everywhere
common_tags = merge(var.extra_tags, {
Environment = var.environment
Terraform = "true"
})
# Conditional logic
instance_type = var.environment == "prod" ? "m5.xlarge" : "t3.micro"
}
Anti-Patterns to Avoid
Don’t use variables for computed values
# BAD - this should be a local
variable "name_prefix" {
default = "myapp-dev" # Hardcoded, computed from other values
}
# GOOD
locals {
name_prefix = "${var.app_name}-${var.environment}"
}
Don’t use locals for user input
# BAD - users can't override this
locals {
region = "us-east-1" # Should be configurable
}
# GOOD
variable "region" {
type = string
default = "us-east-1"
}
Real-World Example
# variables.tf - What users configure
variable "project" { type = string }
variable "environment" { type = string }
variable "instance_count" { type = number; default = 1 }
# locals.tf - What the code computes
locals {
name_prefix = "${var.project}-${var.environment}"
is_production = var.environment == "prod"
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
}
}
# main.tf - Uses both
resource "aws_instance" "app" {
count = var.instance_count # User input
instance_type = local.is_production ? "t3.large" : "t3.micro" # Computed
tags = merge(local.common_tags, { Name = "${local.name_prefix}-${count.index}" })
}
Hands-On Courses
Conclusion
Variables are for external input; locals are for internal computation. Using each correctly leads to cleaner, more maintainable Terraform code. When in doubt, ask: should the user set this value? If yes, use a variable. If no, use a local.

