variable vs locals vs output
Three related but distinct things:
- variable, input. The value comes from outside (CLI, .tfvars, env).
- locals, internal. Computed from other values; visible only within this HCL.
- output, output. The value is visible after apply or to other modules.
If a value comes from outside, use variable. If you use it in several places after computing it, use locals. If you want to expose it externally, use output.
Basic syntax
variable "env" {type = string
default = "dev"
}
variable "app_name" {type = string
default = "my-app"
}
locals { name_prefix = "${var.app_name}-${var.env}" common_tags = {Project = var.app_name
Environment = var.env
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket" "logs" { bucket = "${local.name_prefix}-logs"tags = local.common_tags
}
resource "aws_s3_bucket" "data" { bucket = "${local.name_prefix}-data"tags = local.common_tags
}
Each resource uses the shared prefix and shared tags without repeating itself.
The reference is local.name (no s!). The declaration is locals { ... } (with s).
Locals can reference other locals
locals { base_name = "${var.app_name}-${var.env}" bucket_name = "${local.base_name}-bucket" log_prefix = "${local.bucket_name}/logs/"}
The chain is resolved automatically. Terraform builds a graph and resolves dependencies.
Locals can hold complex expressions
locals {enable_https = var.env == "prod" || var.env == "staging"
azs = data.aws_availability_zones.available.names
instance_count = var.env == "prod" ? 3 : 1
subnet_cidrs = [
for i in range(local.instance_count) :
cidrsubnet(var.vpc_cidr, 8, i)
]
}
Conditions, loops, functions, anything allowed in HCL expressions is fair game. See tf-conditional-expression and tf-functions-collection.
Multiple locals blocks are fine
# general.tf
locals { name_prefix = "${var.app_name}-${var.env}"}
# tags.tf
locals { common_tags = {Project = var.app_name
Environment = var.env
}
}
# network.tf
locals {public_subnet_count = 3
}
A project can have as many locals blocks as needed. Terraform merges them into a single local.* namespace. This is convenient for splitting definitions across files.
One constraint: names across different locals blocks must not collide. local.foo = "a" in one file and local.foo = "b" in another is a validation error.
When to use locals
Good cases:
- Shared name prefix.
${local.name_prefix}-bucket,${local.name_prefix}-role, and so on. - Shared tags. One map of project tags applied to every resource.
- Computations from multiple variables. Building an ARN from
envandregion, for example. - Conditional logic.
local.enable_https = var.env == "prod". - Aliases.
local.azs = data.aws_availability_zones.available.namesas a short name for a long expression.
When you do not need locals:
- If a value is used only once, write it inline.
- If the value comes from outside, it is a variable.
Pitfalls
-
Locals are not for secrets. They are stored in state in plain text, with no
sensitivemarker (sensitive exists only on variable and output). -
Locals cannot reference themselves.
local.x = local.x + 1is an error. Terraform is not a programming language with iterative state. -
Do not overuse them. If half the HCL file is a
localsblock, the code becomes harder to follow. That is a sign some values belong in variables instead. -
localvslocals: a common mix-up. The declaration islocals { ... }(plural). The reference islocal.x(singular, nos). Never writelocals.xwith ans. -
Locals are not available in .tfvars. Only variables go in .tfvars. Locals are computed inside HCL.
-
Renaming a local: does it appear in the diff? No. Locals are not stored in state, only in expressions. Change the name, Terraform recomputes all references, and nothing is sent to the cloud (as long as the resulting value stays the same).