Chapter 4: Variables, Outputs, and Expressions
Topic 24

Outputs

Outputs

Outputs expose values from a configuration or module — an instance's IP, a VPC's ID, a database endpoint — for humans to read after apply and for other modules and stacks to consume. They are the public return values of a module: the deliberate surface other code depends on, as opposed to its internal resources.

The mental model that matters is encapsulation. A module's resources are private. The only way anything outside the module can read a value from inside it is an output you declared. That makes the set of outputs a contract — and a contract you should keep small, because every output is something a consumer can come to depend on and you can no longer freely change.

Declaring an Output

An output block names a value and binds it to an expression — usually a resource attribute. After apply, Terraform prints the outputs, and you can query any one with terraform output vpc_id. The block below publishes a VPC's ID and a load balancer's DNS name for downstream use.

outputs.tf — two outputs
output "vpc_id" {
  value       = aws_vpc.main.id
  description = "ID of the application VPC"
}

output "alb_dns_name" {
  value       = aws_lb.app.dns_name
  description = "Public DNS name of the load balancer"
}

Outputs as a Module's Interface

When you call a child module, its outputs are the only thing you can read from it — module.network.vpc_id works because vpc_id is a declared output, while module.network.aws_vpc.main.id does not, because the resource is private to the module. This is why a module without outputs is a black box: it does its work, but the parent can't wire anything to it.

Outputs are a module's only public surface
module internals
declared outputs
parent / other stack
main.tf — consuming a module's output
module "network" {
  source = "./modules/network"
  cidr   = "10.0.0.0/16"
}

resource "aws_instance" "app" {
  ami       = "ami-0abc123"
  subnet_id = module.network.private_subnet_id
}

Sensitive Outputs

Marking an output sensitive = true suppresses its value from the CLI display after apply, so a generated password or token does not print into a terminal or a retained CI log. As with sensitive variables, this controls display only — the value still lives in state, and a consumer that references the output still receives the real value. Forgetting sensitive on a secret output is a direct way to leak it into searchable logs.

outputs.tf — a sensitive output
output "db_password" {
  value     = aws_db_instance.main.password
  sensitive = true
}

Outputs for Machines

terraform output prints human-formatted text; terraform output -json prints structured JSON that a script can parse reliably. Anything programmatic — a CI step reading an ARN, a wrapper feeding instance IPs into an Ansible inventory — should consume the JSON form. Parsing the human text breaks the first time the display format changes, and it changes between versions.

consuming outputs in a script
# reliable: structured JSON for scripts
terraform output -json > outputs.json

# a single value, raw, for shell use
BUCKET=$(terraform output -raw bucket_name)

Cross-Stack Outputs

An output in one configuration often becomes an input to another. The tight-coupling route is terraform_remote_state, which reads another stack's outputs directly and creates a hidden ordering dependency. The looser route is publishing the value to SSM Parameter Store and reading it as a data source, which decouples the two stacks so the producer can move or restructure without breaking the consumer.

Common Mistakes
  • Trying to reference module.x.aws_instance.y.id from the parent — a module's resources are private, and only declared outputs are reachable.
  • Printing a secret as a non-sensitive output, leaking it into a CI log that is retained and searchable long after the run.
  • Exposing far more outputs than any consumer needs, turning module internals into a brittle public contract you can't change later.
  • Parsing human-formatted terraform output text in a script, which breaks the moment the display format shifts between versions.
  • Coupling two stacks with terraform_remote_state when SSM Parameter Store would have kept them independent.
Best Practices
  • Treat outputs as a deliberate, minimal public interface — output what consumers need and nothing internal.
  • Mark every secret output sensitive = true so it stays out of terminal and CI-log display.
  • Use terraform output -json (or -raw for a single value) for any programmatic consumption.
  • Give each output a description, since it documents the contract a downstream consumer reads.
  • Publish cross-stack values through SSM Parameter Store rather than terraform_remote_state to keep the two configurations loosely coupled.
Comparable tools CloudFormation Outputs with Export and Fn::ImportValue Pulumi stack outputs read across stacks Ansible has no stateful return surface — it is stateless

Knowledge Check

From the parent, you need a value computed inside a child module. How do you get it?

  • Declare an output in the child and read module.name.output — only outputs are exposed
  • Reference the child's resource attribute directly as module.name.aws_x.y from the parent
  • Read the child module's own terraform.tfstate file directly from the parent
  • Pass the computed value back up through a shared input variable

What does sensitive = true on an output change?

  • It suppresses the value from CLI display; the value still lives in state and reaches consumers
  • It encrypts the value so it never appears in the state file in plaintext
  • It blocks other modules and stacks from consuming the output entirely
  • It prevents the output from being queried with the terraform output command at all from then on

Why should a CI script read outputs with terraform output -json rather than the default text?

  • The JSON form is stable to parse; the human text format can change between versions and break the script
  • The JSON form is the only output format that includes the sensitive values a CI script needs
  • The default text form is not available at all in automation, only when running interactively
  • JSON output skips the state refresh step entirely and is therefore meaningfully faster to run in a pipeline

What is the trade-off of reading another stack's outputs via terraform_remote_state instead of SSM Parameter Store?

  • It couples the two configs tightly, creating a hidden dependency that breaks if the upstream state moves
  • It cannot read any upstream output that has been marked as sensitive in the producer stack's configuration
  • It requires both the producer and consumer stacks to share a single state file
  • It only works when both stacks live within a single AWS region

You got correct