Skip to main content

Local Value

A Terraform local value is a named expression that you compute once and reuse throughout a configuration without repeating the calculation. Locals are like constants or intermediate variables — they are not input parameters and cannot be set by callers.

What Is a Terraform Local Value?

A local value is scratch paper. You write a calculation once — like "${var.project}-${var.environment}" — give it a name (name_prefix), and reference that name everywhere you need the result instead of repeating the calculation.

Locals are the DRY (Don't Repeat Yourself) principle for Terraform expressions. If you find yourself writing the same expression in three resource blocks, that expression belongs in a local.

At Swiggy, the infrastructure team uses a single common_tags local that applies consistently to every resource. When the cost accounting team needs a new tag, one line changes in the locals block — not sixty lines across sixty resources.

◈ DIAGRAM
+------------------------------------------+
| Without locals — expression repeated: |
| bucket = "swiggy-${var.env}-data" | <- in S3 resource
| name = "swiggy-${var.env}-app" | <- in EC2 resource
| id = "swiggy-${var.env}-rds" | <- in RDS resource
| 3 places to update when naming changes |
+------------------------------------------+
|
v
+------------------------------------------+
| With locals — computed once: |
| locals { |
| prefix = "swiggy-${var.environment}" |
| } |
| bucket = "${local.prefix}-data" |
| name = "${local.prefix}-app" |
| id = "${local.prefix}-rds" |
| 1 place to update |
+------------------------------------------+

Locals Block Syntax

HCL
# locals.tf — or add locals block anywhere in .tf files
# All locals in one block are evaluated together
locals {
# ── String construction ────────────────────────────────────────────────
name_prefix = "${var.project}-${var.environment}" # swiggy-payments-prod
# ── Tagging ───────────────────────────────────────────────────────────
# Define once, use on every resource
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
Repository = "github.com/swiggy/infrastructure"
CostCenter = "platform-${var.environment}"
}
# ── Conditional logic ──────────────────────────────────────────────────
is_production = var.environment == "prod"
instance_type = local.is_production ? "t3.large" : "t3.micro"
backup_retention = local.is_production ? 30 : 3
multi_az = local.is_production
# ── Map lookups ────────────────────────────────────────────────────────
# Look up instance type for the current environment
ec2_size = lookup(var.instance_types_map, var.environment, "t3.micro")
# ── Computed names ──────────────────────────────────────────────────────
# Locals CAN reference other locals in the same block
s3_bucket_name = "${local.name_prefix}-data-${data.aws_caller_identity.current.account_id}"
rds_id = "${local.name_prefix}-postgres"
sg_name = "${local.name_prefix}-app-sg"
log_group = "/aws/${data.aws_region.current.name}/${local.name_prefix}/app"
# ── List manipulation ──────────────────────────────────────────────────
az_count = length(data.aws_availability_zones.available.names)
azs = slice(data.aws_availability_zones.available.names, 0, 2)
# Flatten list of lists into one list
all_private_cidrs = flatten([var.private_subnet_cidrs, var.db_subnet_cidrs])
# ── Map operations ──────────────────────────────────────────────────────
# Merge common tags with resource-specific tags
rds_tags = merge(local.common_tags, {
Name = local.rds_id
Component = "database"
Engine = "postgres"
})
}

Using Locals in Resources

HCL
# main.tf — locals keep resource blocks clean and DRY
resource "aws_s3_bucket" "data" {
bucket = local.s3_bucket_name # computed once in locals
tags = local.common_tags # map from locals — not repeated here
}
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux.id
instance_type = local.instance_type # conditional — large in prod, micro in dev
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-app"
})
}
resource "aws_db_instance" "main" {
identifier = local.rds_id
multi_az = local.multi_az # true in prod, false in dev
backup_retention_period = local.backup_retention
tags = local.rds_tags
}

Locals vs Variables — When to Use Which

◈ DIAGRAM
+------------------------------------------+
| Use VARIABLES when: |
| - The caller needs to set the value |
| - The value differs per environment |
| - Example: instance_type, environment, |
| db_password, project_name |
+------------------------------------------+
+------------------------------------------+
| Use LOCALS when: |
| - Value is computed from other values |
| - Not settable by the caller |
| - Used in more than one place |
| - Example: name_prefix, common_tags, |
| is_production, s3_bucket_name |
+------------------------------------------+

Advanced Locals Patterns

HCL
locals {
# For expressions inside locals
upper_envs = [for env in ["dev", "staging", "prod"] : upper(env)]
# Convert list to set for for_each
environment_set = toset(["dev", "staging", "prod"])
# Complex conditional
log_level = (
var.environment == "prod" ? "warn" :
var.environment == "staging" ? "info" :
"debug" # dev
)
# Build IAM policy ARNs dynamically
ssm_policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
# CIDR math
public_subnets = [
for i in range(2) : cidrsubnet(var.vpc_cidr, 8, i + 1)
]
# Result: ["10.0.1.0/24", "10.0.2.0/24"] when vpc_cidr = "10.0.0.0/16"
}

Troubleshooting Locals

Error Root Cause Fix
Error: Cycle: local.x -> local.y -> local.x Two locals referencing each other in a loop Restructure — one must not depend on the other
Error: local.x is not defined Typo in local name or missing declaration Check spelling in locals block
local.x returns wrong type Expression result type mismatch Use tostring(), tolist(), or toset() for type coercion
Large locals block is hard to read All locals in one block Split into multiple locals blocks — they can appear anywhere in .tf files
Tip

Add a comment above each local explaining why it exists — not what it computes. # common tags applied to every resource is more useful than # tags map as a comment on the common_tags local.

Remember

Locals cannot be set by callers. If a value needs to be configurable by the person running Terraform (or a CI/CD pipeline), it must be a variable. If it is computed internally from other values, it is a local.

Frequently Asked Questions

How is a Terraform local value different from a variable?

A variable is an input the caller sets — via a .tfvars file, CLI flag, or module argument. A local is computed inside the configuration itself, from expressions, other locals, resource attributes, or variables, and cannot be overridden from outside. Locals exist purely to avoid repeating the same calculation (like a merged tag map or a naming prefix) across multiple resource blocks, keeping the config DRY.

When should you avoid overusing locals in a Terraform module?

Locals are great for one calculation reused several times, but chaining dozens of locals that each reference the previous one turns a module into a hard-to-trace dependency graph, especially for reviewers reading `terraform plan` output. A common mistake is burying business logic (environment-based conditionals, complex merges) several locals deep instead of documenting it, or splitting it into a dedicated module with clear inputs/outputs.