Skip to content
Infrastructure as Code iac variables 4 min read

Local Values

Local values let you assign a name to an expression so you can use it many times within a module without repeating yourself. Where an input variable is a parameter the caller supplies, a local is a value the module computes for its own internal use. They are the Terraform equivalent of a local variable in a function: they keep configurations readable, DRY, and easy to change in one place. Locals work identically in OpenTofu, since both share the same HCL2 expression language.

Declaring locals

You declare locals inside a locals block. Each entry is a name and an expression — and the expression can reference variables, resources, data sources, functions, and even other locals.

locals {
  name_prefix = "${var.project}-${var.environment}"
  is_prod     = var.environment == "production"

  common_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

You can declare multiple locals blocks in a module and they are all merged together, so it is common to group related locals into separate blocks. Within a single block, ordering does not matter — Terraform resolves dependencies between locals automatically through its dependency graph.

Referencing locals

You read a local with the local. prefix (singular local, even though the block is locals). References are valid anywhere an expression is allowed: resource arguments, outputs, other locals, and string interpolation.

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = local.is_prod ? "m6i.large" : "t3.micro"

  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-app"
  })
}

resource "aws_s3_bucket" "assets" {
  bucket = "${local.name_prefix}-assets"
  tags   = local.common_tags
}

Here the common_tags map is defined once and applied to every resource via merge, and the naming convention lives in a single name_prefix. Change the convention once and every resource follows.

DRYing up repeated values

The clearest win for locals is eliminating duplication. Without them, a common tag set or a derived name gets copied into every resource, and the copies drift apart over time. Compare the two approaches:

locals {
  tags = {
    Team        = "platform"
    Environment = var.environment
    CostCenter  = "eng-1042"
  }
}

resource "aws_dynamodb_table" "sessions" {
  name         = "${var.environment}-sessions"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "id"

  attribute {
    name = "id"
    type = "S"
  }

  tags = local.tags
}

resource "aws_sqs_queue" "events" {
  name = "${var.environment}-events"
  tags = local.tags
}

If the cost center changes, you edit one line instead of hunting through the whole module.

Tip: Locals are evaluated lazily and only when referenced, so an unused local costs nothing. But a local that is never referenced is dead code — prune it, since terraform validate will not warn you about it.

Locals versus variables

Locals and variables look similar but solve opposite problems. Use this table to decide which you need:

AspectInput variable (var.)Local value (local.)
Set byThe module’s callerThe module itself
OverridableYes — CLI, .tfvars, env varsNo — fixed by the expression
Typical useParameters / configuration knobsDerived or repeated computed values
Can reference resourcesNoYes
Has a type / validationYesNo (inferred from the expression)

A useful rule of thumb: if you want someone to be able to change the value when they call your module, make it a variable. If the value is something you derive from those inputs, make it a local.

variable "environment" {
  type        = string
  description = "Deployment environment name."
}

locals {
  # Derived from the input — not something a caller should override directly.
  retention_days = var.environment == "production" ? 90 : 7
}

resource "aws_cloudwatch_log_group" "app" {
  name              = "/app/${var.environment}"
  retention_in_days = local.retention_days
}

Inspecting locals

Locals do not appear in plan output on their own, but you can surface a computed local through an output or evaluate it interactively with terraform console.

echo 'local.name_prefix' | terraform console

Output:

"acme-production"

This is handy for confirming that a complex expression — a for loop, a merge, or a ternary — produces exactly the value you expect before you wire it into a resource.

Best Practices

  • Reach for a local whenever the same expression appears more than once, especially tag maps and name prefixes.
  • Keep locals focused on derivation and reuse; do not use them to fake user-overridable settings — that is a variable’s job.
  • Group related locals into named locals blocks (for example one for tags, one for naming) to keep large modules readable.
  • Name locals for the concept they represent (name_prefix, is_prod), not the expression mechanics.
  • Avoid deep chains of locals referencing locals referencing locals — a couple of levels aid clarity, but more obscure it.
  • Use terraform console to verify non-trivial local expressions before relying on them.
Last updated June 14, 2026
Was this helpful?