Chapter 4: Variables, Outputs, and Expressions
Topic 25

Local Values

Expressions

A local value names an expression so you compute it once and reuse it — a common tag map, a constructed name prefix, a derived list. Where variables are inputs from outside, locals are internal intermediate values. Used well they remove repetition and name intent; over-used they add a layer of indirection that makes a config harder to read, not easier.

Input variable vs local value
Input variable
External — set by the caller via tfvars, flags, or env. The module's interface, read as var.name.
Local value
Internal — derived inside the config. Can reference variables, resources, and data sources, read as local.name.

The single most common good use is tagging. Every resource in an AWS account should carry a consistent set of tags, and copying that map onto 40 resources is both noisy and a guarantee that one of them drifts. A common_tags local computed once and merged everywhere fixes that, and it is the pattern you will reach for in nearly every configuration.

Declaring Locals

A locals block holds one or more named assignments, referenced as local.name. The values can be literals or any expression, and a local may reference other locals, variables, resources, and data sources. The block below builds a name prefix from two variables and a tag map that reuses it.

locals.tf — derived values
locals {
  name_prefix = "${var.project}-${var.environment}"

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

The common_tags Pattern

You apply the shared tags by merging the local into each resource's own tags with the merge function, so every resource gets the base set plus anything specific to it. Change a tag in one place — the local — and it propagates to every resource on the next apply, instead of being edited 40 times by hand.

main.tf — merging common tags
resource "aws_instance" "app" {
  ami           = "ami-0abc123"
  instance_type = "t3.micro"
  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-app"
    Role = "web"
  })
}

DRY Without Over-Abstraction

A local earns its place when an expression repeats or when naming it clarifies intent. The opposite — wrapping a single literal that appears once behind a local — adds an indirection that makes a reader jump to the definition to learn what is otherwise sitting right there. The test is whether the name explains more than the expression it replaces; if it doesn't, inline it.

Locals Compared to Variables

The dividing line is direction. A variable is set from outside and a caller controls it, but it cannot reference resources or data sources. A local is computed inside the config, cannot be set from outside, and can reference anything — variables, resources, data sources, and other locals. Use a variable for what comes in, a local for what you derive from it.

Keeping Locals Shallow

Locals can reference other locals, which is useful for one or two steps and a trap past that. A chain where understanding local.a means reading local.b, which means reading local.c, has turned a derivation into a puzzle. Keep the dependency depth shallow; if a local needs three other locals to make sense, the abstraction is hiding the data flow rather than clarifying it.

Local value vs input variable

Input variable — an external input the caller sets, with a type and optional default. It cannot reference resources or data sources. Use it for anything that comes in from outside: a region, an environment name, an instance count.

Local value — an internal computed value the caller can't set, which can reference variables, resources, data sources, and other locals. Use it for anything you derive inside the config: a name prefix, a merged tag map, a list reshaped from inputs.

Common Mistakes
  • Turning every literal into a local, adding indirection that makes a reader chase a definition for a value that appears exactly once.
  • Trying to override a local from outside the module — you can't; an external input is what a variable is for.
  • Building a deep chain of locals referencing locals until following the data flow means reading five definitions to understand one.
  • Duplicating the same tag map across dozens of resources instead of a single common_tags local, so one resource silently drifts.
  • Putting a value a caller should control into a local, forcing an edit to the module's code instead of passing a different input.
Best Practices
  • Define a common_tags local and merge it into every resource for consistent, single-source tagging.
  • Introduce a local when an expression repeats or when a name clarifies intent, not reflexively for every value.
  • Keep locals shallow and readable; if a local needs three other locals to understand, reconsider the abstraction.
  • Reach for a variable for an external input and a local for an internal derivation — don't blur the two roles.
  • Build a name_prefix local once and reuse it for resource names so naming stays consistent across the stack.
Comparable tools CloudFormation has no true locals — Mappings and pseudo-parameters partly fill the gap Pulumi uses ordinary host-language variables Ansible set_fact defines intermediate values within a play

Knowledge Check

What is the core difference between a local value and an input variable?

  • A variable is set from outside and can't reference resources; a local is computed inside and references anything
  • A local is set from outside the module by the caller; a variable is the one computed internally from expressions
  • They are fully interchangeable — the two keywords are simply aliases for the same construct
  • A variable can reference resource attributes directly but a local cannot

Which is a genuinely good use of a local?

  • A common_tags map merged into every resource so a tag change happens in one place
  • Wrapping a single string literal that appears only once, purely to give it a friendly name
  • Exposing a value that a caller of the module needs to set differently per environment
  • Storing a secret in a local so it never appears in the state file

Why can't you set a local value from outside the module?

  • Locals are internal derivations by design; external inputs are exactly what variables are for
  • Locals are read-only constants that Terraform freezes and computes just once at provider install time
  • You actually can, by passing the -local=name=value flag on the command line
  • Locals are encrypted at rest, so external code has no way to write to them

What is the downside of a deep chain of locals each referencing the previous one?

  • Understanding one value means reading several definitions, hiding the data flow rather than clarifying it
  • Terraform flatly refuses to evaluate any local that references another local declared in the same locals block
  • It forces every resource that depends on the chain to be recreated on each apply
  • A deeply chained local can no longer be referenced from a resource's tags

You got correct