TerraformXIII · Variables, Outputs, and LocalsOutputs
Outputs: The Configuration Interface
What you'll learn
- Declare outputs with descriptive documentation
- Use outputs to expose useful information to operators and consumers
- Mark sensitive outputs correctly
- Use structured output for machine consumption
Prerequisites
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
An output is the configurations return statement. It exposes
information about the resources to operators (via
terraform output) and to other configurations (via the
terraform_remote_state data source). A well-designed output
interface is the difference between a configuration that
supports multi-environment use and one that requires a
duplication per environment.
Declaring an output
output "instance_id" {
value = aws_instance.web.id
description = "The EC2 instance ID of the web server."
}
The fields:
- value — the expression to evaluate. Most outputs are references to attributes of resources.
- description — human-readable documentation. Required in production.
What makes a good output
A good output:
- Has a clear single purpose. One output, one value.
- Has documentation. Every output has a
description. - Is useful. The output is consumed by an operator or another configuration.
- Has a stable name. Renaming an output breaks every consumer.
# Good: focused, documented
output "vpc_id" {
value = aws_vpc.main.id
description = "The ID of the VPC."
}
output "public_subnet_ids" {
value = aws_subnet.public
description = "The public subnets, keyed by availability zone."
}
# Less good: underexposed
output "data" {
value = aws_vpc.main
}
# Less good: too clever
output "everything" {
value = {
vpc = aws_vpc.main
subnets = aws_subnet.public
security_groups = aws_security_group.web
}
}
The “everything” output conflates purpose with payload. The
consumer does not know which attribute to use. The
values schema is not explicit.
Sensitive outputs
A sensitive = true output hides the value in CLI output:
output "database_password" {
value = aws_db_instance.primary.password
description = "The database password."
sensitive = true
}
When sensitive = true is set:
- The CLI output is
(sensitive). - The state file has the value in plain text.
- The plan output also hides the value.
As with sensitive variables, this is a UI affordance, not a security control. The value is in the state. The state is encrypted at rest by the backend. The course has a dedicated lesson on this (Part XVI).
Structured outputs
For complex values, structured outputs are useful:
output "network" {
value = {
vpc_id = aws_vpc.main.id
vpc_cidr = aws_vpc.main.cidr_block
public_subnets = aws_subnet.public
private_subnets = aws_subnet.private
}
description = "Network topology: VPC, subnets, and IDs."
}
The output is a JSON object. The consumer can access the attributes:
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "mycompany-terraform-state"
key = "network/terraform.tfstate"
}
}
resource "aws_instance" "web" {
ami = "ami-0e1bed4f"
subnet_id = data.terraform_remote_state.network.outputs.network.public_subnets["a"].id
}
The structured output is the contract between the producer and the consumer. Renaming the output breaks every consumer.
Output for operators
The CLI uses outputs to communicate information to operators:
terraform output
The output:
instance_id = "i-0abc123def456789"
public_dns = "ec2-1-2-3-4.compute-1.amazonaws.com"
The operator can read the output without parsing the state.
# Output as JSON
terraform output -json
# A specific output
terraform output -raw database_endpoint
The -raw flag is useful for scripting. The output is the
literal value, with no quotes.
Output for cross-stack consumption
The output is the contract between two configurations:
# In the network configuration
output "vpc_id" {
value = aws_vpc.main.id
description = "The ID of the VPC."
}
# In the compute configuration
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "mycompany-terraform-state"
key = "network/terraform.tfstate"
}
}
resource "aws_instance" "web" {
ami = "ami-0e1bed4f"
subnet_id = data.terraform_remote_state.network.outputs.vpc_id
}
The contract is the output name. Renaming the output breaks the consumer. The course has a dedicated lesson on remote state (Part XIII).
What not to output
A few patterns that should be avoided:
Sensitive values. Use a secrets manager instead. The state is the wrong place for credentials.
Computed values that are not used. Outputs are not free. Each output is stored in state and computed on every apply.
Implementation details. The consumer does not need to know the AMI ID; the consumer needs to know the instance ID.
Large collections. A for_each over a hundred resources
produces a large output. Consider exposing counts or summaries
instead.
Outputs and state
The output is in the state. The state is the source of truth for the output. The output is computed during the apply.
# Refresh the output
terraform refresh
# Show the output
terraform output
The output is the value as of the last apply. The output is not dynamic; it does not query the provider.
What comes next
The next lesson is providers — the plugins that translate Terraforms resource operations into API calls against a specific platform.
Verification
Knowledge check · 7 questions
Q1. What is the role of variables in Terraform?
Q2. What is the role of locals?
Q3. Variables should be sensitive when they contain secrets.
Q4. What is the highest-priority source of variable values?
Q5. Which of the following are valid variable types? (Select all that apply.)
Q6. What is the role of validation in a variable?
Q7. A team uses .tfvars files in Git for production. What is the risk?
Passing score: 75%. Answers are checked in this browser.