There’s a moment in every Terraform project where things start to feel… repetitive. The same tags, naming conventions, or computed values appear across multiple resources. That’s usually the point where locals stop being optional and start becoming essential.
Terraform locals are often described as “just variables,” but that undersells them. They’re more like a lightweight computation layer inside your configuration—perfect for shaping data, avoiding duplication, and making your code easier to reason about.
What Are Terraform Locals, Really?
At a glance, locals are defined using a locals block and referenced with local.<name>. Unlike input variables, they aren’t passed in—they’re computed within the module.
Here’s a simple example:
1locals {
2 project_name = "billing-service"
3 environment = "prod"
4 full_name = "${local.project_name}-${local.environment}"
5}You can then use them anywhere:
1resource "aws_s3_bucket" "app_bucket" {
2 bucket = local.full_name
3}This might seem small, but it scales quickly. Imagine updating naming logic in one place instead of across 20 resources.
Why Not Just Use Variables?
This is a common question. Variables and locals solve different problems:
- Variables are inputs to your module (external configuration)
- Locals are internal transformations (derived values)
Think of locals as the “processing layer” between raw inputs and resource definitions.
Building Smarter Configurations with Locals
Here’s where things get interesting—locals aren’t limited to static values. You can use expressions, conditionals, loops, and functions.
Example: Dynamic Tagging Strategy
1variable "environment" {
2 type = string
3}
4
5locals {
6 common_tags = {
7 Project = "billing"
8 Environment = var.environment
9 }
10
11 extra_tags = var.environment == "prod" ? {
12 Critical = "true"
13 } : {}
14
15 all_tags = merge(local.common_tags, local.extra_tags)
16}Then apply it:
1resource "aws_instance" "app" {
2 ami = "ami-123456"
3 instance_type = "t3.micro"
4
5 tags = local.all_tags
6}This keeps your tagging logic centralized and expressive.
Reducing Duplication Without Losing Clarity
A common mistake developers make is overusing locals for everything. The goal isn’t to hide logic—it’s to clarify intent.
Bad pattern:
1locals {
2 a = "us-east-1"
3 b = local.a
4 c = local.b
5}Good pattern:
1locals {
2 region = "us-east-1"
3}Use locals to make code more readable, not more abstract than necessary.
Transforming Complex Data
Locals shine when working with structured data like maps and lists.
Example: Flattening a Nested Structure
1variable "services" {
2 default = {
3 api = {
4 ports = [80, 443]
5 }
6 worker = {
7 ports = [8080]
8 }
9 }
10}
11
12locals {
13 service_ports = flatten([
14 for name, svc in var.services : [
15 for port in svc.ports : {
16 name = name
17 port = port
18 }
19 ]
20 ])
21}This gives you a clean list of service-port pairs, which can be used in for_each loops.
Locals + for_each = Cleaner Resources
Instead of embedding complex expressions directly inside resources, move them into locals.
For example:
1locals {
2 instance_configs = {
3 web = { instance_type = "t3.micro" }
4 api = { instance_type = "t3.small" }
5 }
6}
7
8resource "aws_instance" "nodes" {
9 for_each = local.instance_configs
10
11 ami = "ami-123456"
12 instance_type = each.value.instance_type
13
14 tags = {
15 Name = each.key
16 }
17}This keeps your resource block clean and focused.
A Few Subtle Gotchas
Locals are powerful, but there are a few nuances worth knowing:
- They are immutable: once defined, you can’t override them
- Evaluation is lazy: Terraform computes them only when needed
- No dependency cycles: locals cannot reference each other circularly
Also, locals are scoped to a module. You can’t access them from outside—use outputs if you need to expose values.
When Should You Reach for Locals?
Some practical use cases:
- Standardizing naming conventions across resources
- Combining multiple variables into a single computed value
- Building reusable tag maps
- Preprocessing data for
for_eachorcount - Simplifying long expressions inside resources
If you find yourself repeating logic or writing unreadable inline expressions, locals are usually the fix.
A Pattern That Scales Well
In larger Terraform modules, it’s common to group locals logically:
1locals {
2 naming = {
3 prefix = "billing"
4 suffix = var.environment
5 }
6
7 computed_names = {
8 bucket = "${local.naming.prefix}-${local.naming.suffix}-bucket"
9 db = "${local.naming.prefix}-${local.naming.suffix}-db"
10 }
11}This approach makes your configuration easier to scan and maintain.
Final Thoughts
Terraform locals aren’t flashy, but they’re one of the most effective tools for keeping infrastructure code clean and maintainable. They help you express intent, reduce duplication, and centralize logic without introducing unnecessary complexity.
If your Terraform files are starting to feel cluttered or repetitive, adding a thoughtful locals block is often the simplest way to bring order back.