Terraform locals name expressions for reuse inside a module; they are ideal for derived values and normalization, but they should not hide choices callers need to control.
Good locals reduce repetition and make intent obvious. Bad locals create a second configuration system that reviewers must decode before they can understand one resource.
Table of contents
- Define and reference local values
- Use locals to normalize inputs
- Know when a variable is better
- Avoid locals that obscure the graph
- Test locals in the console and plan
- How this fits the rest of the stack
- FAQ
Define and reference local values
locals {
service_name = "${var.project}-${var.environment}"
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
}
}
# Reference with singular local
name = local.service_name
You may define multiple locals blocks; Terraform treats them as declarations within the module rather than sequential assignments. Local values can reference variables, data sources, resources, functions, and other locals when no dependency cycle is created.
Use locals to normalize inputs
Normalize optional or flexible caller input once, then give resources one stable internal shape. Merge mandatory tags with caller tags, derive consistent names, or select an environment-specific setting. This is more maintainable than repeating merge and conditional expressions on every resource.
locals {
tags = merge(var.extra_tags, {
Project = var.project
Env = var.environment
})
is_production = var.environment == "prod"
}
Define merge precedence intentionally. In this example mandatory tags win because they appear last. Reverse the order only if callers are allowed to override governance fields.
Know when a variable is better
A variable represents a choice at the module boundary. A local represents a value the module derives or owns. If a team may legitimately choose the value per module call, expose a typed variable with validation. If every caller must repeat the same formula, keep it local.
Do not move a hard-coded region, account, or production size into locals and call the module configurable. The value remains hidden policy. Important environment decisions should be explicit inputs or provider configuration.
Avoid locals that obscure the graph
Long chains of local aliases force readers to jump through files without reducing complexity. Keep related locals near one another, name them for meaning rather than type, and avoid reconstructing entire resource objects unless that structure genuinely simplifies iteration.
Locals are evaluated expressions, not mutable variables. You cannot assign to them later. If a value depends on conditional resource creation, design the collection and references so both branches remain type-consistent and plan-time behavior is understandable.
Test locals in the console and plan
terraform fmt -check
terraform validate
terraform console
> local.service_name
> local.common_tags
Use console to inspect pure expressions and terraform plan to inspect values involving resources or unknown data. Add module tests for naming, tag precedence, and environment branches. A local that saves three lines but surprises the plan is not a simplification.
- Keep module choices in variables
- Keep derived normalization in locals
- Use clear types and validations
- Centralize tag and naming policy
- Avoid multi-hop aliases
- Inspect output in plans and tests
Locals also deserve the same review discipline as module inputs. A local derived from resource attributes can remain unknown during planning, so do not use it where Terraform needs a fixed collection shape for for_each unless the keys are known. Keep sensitive values sensitive through every transformation; assigning a secret to a local does not make it safe to print in outputs or error messages. When locals build names, account for provider length and character constraints before resources are created, and test collisions across environments. When they build tag maps, make ownership and precedence visible in one place. A useful refactor test is to inline the expression mentally: if doing so makes the resource easier to understand, the local is probably indirection rather than abstraction. If the same policy appears across modules, promote it into a shared module contract or generated convention instead of copying a locals block that will drift.
How this fits the rest of the stack
Terraform locals shape resources that eventually consume real capacity. The RunxBuild hosting calculator helps model that capacity before apply, and the RunxBuild dashboard keeps the resulting services and logs visible.
Useful related references:
- Terraform Replace: lifecycle.replace_triggered_by and -replace
- Terraform templatefile(): Templates for Config Files and Scripts
- Terraform Remove Block: Stop Managing a Resource Without Destroying It
- Services on RunxBuild
FAQ
What is a Terraform local value?
It is a named expression available inside one module and referenced through the singular local object.
Can a local be changed later?
No. Locals are evaluated expressions, not mutable variables or assignment slots.
When should I use a variable instead?
Use a variable when callers should choose the value. Use a local when the module derives or owns it.
Can locals reference resources?
Yes, but the value may remain unknown until planning or apply depending on the resource attribute.
Can I have multiple locals blocks?
Yes. Group them for readability; declaration order does not make them sequential assignments.