Outputs and Locals
The idea in one sentence
Section titled “The idea in one sentence”A local is a named expression computed once and reused inside a configuration, while an output takes a value out of a configuration and makes it visible after apply — together with variable, they are the three distinct kinds of named value in Terraform.
Locals: computing a value once and reusing it
Section titled “Locals: computing a value once and reusing it”A variable is set by whoever calls the configuration, and an output is exposed to whoever consumes it. A local is neither — it is an internal convenience, a name you give to an expression so you do not have to repeat that expression every time you need its result. Nobody outside the configuration can set a local, and nobody outside the configuration can read one directly either.
locals { name_prefix = "${var.environment}-${var.project}"
common_tags = { Project = var.project Environment = var.environment ManagedBy = "terraform" }
instance_count = var.environment == "prod" ? 5 : 2}
resource "aws_instance" "web" { count = local.instance_count ami = var.ami_id instance_type = var.instance_type
tags = merge(local.common_tags, { Name = "${local.name_prefix}-web-${count.index}" })}
resource "aws_s3_bucket" "app_data" { bucket = "${local.name_prefix}-app-data" tags = local.common_tags}local.common_tags is computed once from var.project and var.environment, then referenced by both aws_instance.web and aws_s3_bucket.app_data. Without it, the same merge(...) expression and the same three key-value pairs would have to be copied into every resource block that needs them, and a later change to the tagging scheme would mean hunting down every copy. local.instance_count does the same job for a small conditional: the decision “5 in prod, 2 elsewhere” is written once and referenced by name wherever it is needed. A local is never assigned by a caller and never appears in terraform plan as something you can override — it is purely a name for a computed value inside this configuration.
Outputs: exposing a value after apply
Section titled “Outputs: exposing a value after apply”An output block takes a value that exists inside a configuration — usually an attribute of a resource that only becomes known after apply — and makes it available outside that configuration. description documents what the value means, and sensitive = true masks it from ordinary CLI output the same way it does on a variable.
output "web_instance_ids" { description = "IDs of the EC2 instances created for the web tier" value = aws_instance.web[*].id}
output "app_data_bucket_arn" { description = "ARN of the S3 bucket used for application data" value = aws_s3_bucket.app_data.arn}
output "db_password" { description = "Master password generated for the RDS instance" value = aws_db_instance.main.password sensitive = true}# List every output and its valueterraform output
# Print one output's value, unquoted for scriptingterraform output web_instance_ids
# Machine-readable output, for piping into jq or another toolterraform output -json app_data_bucket_arnterraform output on its own prints every declared output after the most recent apply. terraform output <name> prints just one, and terraform output -json renders the value (or all values, if you omit a name) as JSON, which is what you want when a CI pipeline or another script needs to consume the result programmatically rather than read it as a human. The sensitive = true argument on db_password behaves exactly like it does on a variable: Terraform prints (sensitive value) in plan/apply output and in a bare terraform output, but terraform output db_password still prints the real value when you ask for it by name, and the value is still stored in plain text in state. It is display protection for terminals and logs, not access control.
Outputs as the mechanism for composition
Section titled “Outputs as the mechanism for composition”Inside a single root configuration, an output is mostly informational — a convenient way to surface a value for a human running terraform apply to glance at. Its real importance shows up once a configuration stops being a single, self-contained thing. When you split infrastructure into modules, a module has no other way to hand a computed value back to whoever called it except through its output blocks — the Modules module later in this course covers module.<name>.<output> references in depth. When you split infrastructure into independently-deployed Terragrunt units instead, a dependency block reads another unit’s outputs to wire units together without ever hard-coding a value between them — that pattern gets its own treatment in a later Terragrunt module. In both cases, the mechanism is the same one shown here: a value becomes known inside one configuration, an output block exposes it, and something else — a parent module or a dependent unit — consumes it.
flowchart LR ve["var.environment"] --> lp["local.name_prefix"] vp["var.project"] --> lp lp --> ri["aws_instance.web"] lp --> rb["aws_s3_bucket.app_data"] rb --> ob["output app_data_bucket_arn"] ob --> dep["dependency block or module output reference"]