Locals are computed values within your Terraform configuration — think of them as named constants or intermediate variables. They reduce duplication, make complex expressions readable, and keep your code DRY.
Basic Syntax
locals {
project = "myapp"
environment = terraform.workspace
region = "us-east-1"
# Computed from other locals
name_prefix = "${local.project}-${local.environment}"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "${local.name_prefix}-vpc" # "myapp-prod-vpc"
}
}
Locals vs Variables
| Feature | variable | locals |
|---|---|---|
| Set by | User (tfvars, CLI, env) | Terraform author (in code) |
| Purpose | Configurable inputs | Computed/derived values |
| Overridable | ✅ Yes | ❌ No (fixed in code) |
| Validation | ✅ validation block | ❌ No |
| Reference | var.name | local.name |
# Variable: user provides the value
variable "environment" {
type = string
default = "dev"
}
# Local: you compute the value
locals {
is_production = var.environment == "prod"
instance_type = local.is_production ? "t3.xlarge" : "t3.micro"
}
Rule: If the user should be able to change it → variable. If it’s derived from other values → locals.
Common Patterns
1. Standard Tags
locals {
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
Team = var.team
CostCenter = var.cost_center
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.al2023.id
instance_type = "t3.micro"
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-web"
Role = "web-server"
})
}
resource "aws_s3_bucket" "data" {
bucket = "${local.name_prefix}-data"
tags = local.common_tags
}
resource "aws_rds_instance" "main" {
identifier = "${local.name_prefix}-db"
# ...
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-db"
Backup = "daily"
Snapshot = "true"
})
}
Every resource gets consistent tags without repeating the map.
2. Naming Conventions
locals {
name_prefix = "${var.project}-${var.environment}"
# Resource-specific names
vpc_name = "${local.name_prefix}-vpc"
cluster_name = "${local.name_prefix}-ecs"
db_name = "${local.name_prefix}-db"
bucket_name = "${local.name_prefix}-data-${data.aws_caller_identity.current.account_id}"
}
3. Conditional Logic
locals {
is_production = var.environment == "prod"
# Different configs per environment
instance_type = local.is_production ? "t3.xlarge" : "t3.micro"
multi_az = local.is_production ? true : false
min_capacity = local.is_production ? 3 : 1
max_capacity = local.is_production ? 10 : 2
enable_monitoring = local.is_production
backup_retention = local.is_production ? 30 : 7
}
resource "aws_db_instance" "main" {
instance_class = local.instance_type
multi_az = local.multi_az
backup_retention_period = local.backup_retention
monitoring_interval = local.enable_monitoring ? 60 : 0
}
4. Data Transformation
variable "subnet_config" {
default = {
public = {
cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public = true
}
private = {
cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
public = false
}
}
}
locals {
azs = data.aws_availability_zones.available.names
# Flatten into a map for for_each
all_subnets = merge([
for tier, config in var.subnet_config : {
for i, cidr in config.cidrs :
"${tier}-${local.azs[i]}" => {
cidr = cidr
az = local.azs[i]
tier = tier
public = config.public
}
}
]...)
}
resource "aws_subnet" "all" {
for_each = local.all_subnets
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr
availability_zone = each.value.az
map_public_ip_on_launch = each.value.public
tags = {
Name = "${local.name_prefix}-${each.key}"
Tier = each.value.tier
}
}
5. Computed CIDR Blocks
locals {
vpc_cidr = "10.0.0.0/16"
public_subnets = {
for i, az in local.azs :
az => cidrsubnet(local.vpc_cidr, 8, i)
}
private_subnets = {
for i, az in local.azs :
az => cidrsubnet(local.vpc_cidr, 8, i + 100)
}
database_subnets = {
for i, az in local.azs :
az => cidrsubnet(local.vpc_cidr, 8, i + 200)
}
}
6. Merging Configuration
variable "additional_tags" {
type = map(string)
default = {}
}
locals {
# Base tags + user-provided tags + auto-generated tags
all_tags = merge(
local.common_tags,
var.additional_tags,
{
LastUpdated = timestamp()
}
)
}
Multiple locals Blocks
You can have multiple locals blocks — Terraform merges them:
# locals-naming.tf
locals {
name_prefix = "${var.project}-${var.environment}"
}
# locals-tags.tf
locals {
common_tags = {
Project = var.project
Environment = var.environment
}
}
# locals-network.tf
locals {
vpc_cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
Anti-Patterns
Don’t Over-Abstract
# ❌ Too many levels of indirection
locals {
a = var.x
b = local.a
c = local.b
d = local.c # What is this even?
}
# ✅ Direct and readable
locals {
instance_type = var.environment == "prod" ? "t3.xlarge" : "t3.micro"
}
Don’t Use Locals for Simple Values
# ❌ Unnecessary — just use the variable directly
locals {
region = var.region
}
# ✅ Use local only when you're computing something
locals {
region_short = replace(var.region, "-", "") # "useast1"
}
Hands-On Courses
- Terraform for Beginners on CopyPasteLearn
- Terraform By Example — practical code examples
Conclusion
Locals keep your Terraform code DRY and readable. Use them for standard tags, naming conventions, conditional logic, and data transformation. Don’t use them as variable aliases — only create a local when you’re computing or deriving a value. The most common pattern is common_tags + name_prefix — these two locals alone eliminate hundreds of lines of duplication in a typical project.