Surface useful values after an apply with outputs, mark sensitive ones so they stay out of logs, and tidy repeated expressions into named local values.
Why: outputs surface information after an apply — an IP address, a generated name, a connection string — printed in the terminal and readable by other tooling. They also expose a child module's values to its parent (the Modules lesson). Define what is worth knowing once the infrastructure exists.
resource "random_pet" "server" {
length = 2
}
output "server_name" {
value = random_pet.server.id
description = "The generated server name"
}After apply, outputs are stored in state and printed. terraform output reprints them anytime; -raw gives a single value unquoted (handy for piping into another command); -json gives all of them machine-readable.
Show all outputs
terraform outputOne value, unquoted — pipe it into another tool
terraform output -raw server_nameEverything, as JSON
terraform output -jsonWhy: a generated password or token should never land in plan output or CI logs. sensitive = true redacts the value in the terminal (it shows as <sensitive>). Note: it is still stored in plaintext in state — protect the state itself, covered in the State lesson.
resource "random_password" "db" {
length = 20
}
output "db_password" {
value = random_password.db.result
sensitive = true # redacted in CLI output and plans
}Why: a local is a named value computed once and reused, so you do not repeat the same expression or string-building everywhere. Reference it as local.name. Great for common tags, a name prefix, or any derived value — change it in one place.
locals {
name_prefix = "myapp-${var.environment}"
common_tags = {
project = "myapp"
environment = var.environment
managed_by = "terraform"
}
}
resource "local_file" "readme" {
filename = "${local.name_prefix}-readme.txt"
content = "tags: ${jsonencode(local.common_tags)}"
}Why: a precondition checks an assumption before a resource or output is used, failing with a clear message if it does not hold — catching problems early rather than shipping something wrong. Put it in a lifecycle block on a resource, or directly on an output.
output "server_name" {
value = random_pet.server.id
precondition {
condition = length(random_pet.server.id) > 0
error_message = "server name must not be empty."
}
}