Outputs and Locals
The idea in one sentence
Section titled “The idea in one sentence”An output exposes a value out of a Terraform configuration after apply, while a local computes a named expression once for reuse inside the same configuration — neither one is a variable, and each solves a different problem.
Declaring an output block
Section titled “Declaring an output block”An output block takes a name, a value expression, and optionally a description and sensitive flag. After terraform apply finishes, every declared output is printed, and you can query it again later without re-running apply.
resource "google_compute_instance" "web" { name = "web-server" machine_type = "e2-medium" zone = "us-central1-a"
boot_disk { initialize_params { image = "debian-cloud/debian-12" } }
network_interface { network = "default" access_config {} }}
output "instance_id" { description = "The unique identifier Google Cloud assigned to the instance" value = google_compute_instance.web.instance_id}
output "instance_external_ip" { description = "Public IP address of the web server" value = google_compute_instance.web.network_interface[0].access_config[0].nat_ip}
output "db_password" { description = "Generated password for the application database user" value = random_password.db.result sensitive = true}# Print every output declared in the configurationterraform output
# Print a single output by name, unquoted for use in scriptsterraform output instance_external_ip
# Print all outputs as JSON, for piping into another toolterraform output -jsonterraform output -json matters more than it looks: it turns your outputs into structured data that another script, a CI pipeline, or a wrapper tool can parse reliably, instead of scraping human-formatted terminal text. The sensitive = true flag on db_password works exactly like it does on a variable — Terraform masks the value in terraform output and in apply output, but the value is still written to the state file in plain text. It is a display protection, not an access control.
locals — the third kind of named value
Section titled “locals — the third kind of named value”Terraform has three distinct kinds of named values, and it is worth being precise about the difference. A variable is set by the caller from outside. An output is exposed to the outside after apply. A local is neither: it is a named expression computed once, inside the configuration, purely so you do not have to repeat that same expression in five different resource blocks.
variable "environment" { type = string description = "Deployment environment name" default = "dev"}
variable "project_id" { type = string description = "GCP project ID"}
locals { name_prefix = "${var.project_id}-${var.environment}"
common_labels = { environment = var.environment managed_by = "terraform" }}
resource "google_compute_instance" "web" { name = "${local.name_prefix}-web" machine_type = "e2-medium" zone = "us-central1-a" labels = local.common_labels
boot_disk { initialize_params { image = "debian-cloud/debian-12" } }
network_interface { network = "default" access_config {} }}
resource "google_storage_bucket" "assets" { name = "${local.name_prefix}-assets" location = "US" labels = local.common_labels}Without local.name_prefix and local.common_labels, that same "${var.project_id}-${var.environment}" string interpolation and that same labels map would have to be typed out again on every resource that needs them, and a later change to the naming scheme would mean hunting down every copy. A locals block is not user-settable the way a variable is, and it is not visible outside the configuration the way an output is — it exists purely to keep a computed expression in exactly one place.
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 print a value at the end of apply, or to feed a script via terraform output -json. Its real importance shows up once you start splitting infrastructure into smaller, separately-managed pieces.
When one Terraform module calls another, the calling module reads the child module’s outputs to wire values between them — a networking module’s output "subnet_self_link" becomes the input to a compute module’s variable "subnet". The Modules module later in this course covers that composition in depth. The same idea shows up again, at a larger scale, in Terragrunt: an independently-applied Terragrunt unit reads another unit’s outputs through a dependency block, so a VPC deployed in one directory can hand its network name to a database deployed in a completely separate directory, with no shared Terraform state. Outputs are what make both of these forms of composition possible — without a defined, stable output, there is nothing for the consuming module or unit to read.
flowchart LR v1["var.project_id"] --> l["local.name_prefix"] v2["var.environment"] --> l l --> r1["google_compute_instance.web"] l --> r2["google_storage_bucket.assets"] r1 --> o["output ip_address"] o --> ext["Consumed by another module or a Terragrunt dependency block"]