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.
+------------------------------------------+| 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
# 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
# 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
+------------------------------------------+| 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
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 |
TipAdd a comment above each local explaining why it exists — not what it computes.
# common tags applied to every resourceis more useful than# tags mapas a comment on thecommon_tagslocal.
RememberLocals 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.