Why outputs exist
When Terraform creates a resource, that resource gains attributes: id, arn, endpoint. Those values are often needed by:
- The user, to see which URL the new service received.
- Another Terraform project, via remote state.
- An external script:
aws_endpoint=$(terraform output -raw endpoint). - The parent module, when a child module returns its values upward.
An output is how you expose a value to the outside world.
Basic syntax
resource "aws_s3_bucket" "demo" {bucket = "my-bucket-12345"
}
output "bucket_arn" {value = aws_s3_bucket.demo.arn
description = "ARN of the created bucket: needed for cross-account IAM policies."
}
output "bucket_url" { value = "https://${aws_s3_bucket.demo.bucket}.s3.amazonaws.com"}
After apply Terraform prints:
Outputs:
bucket_arn = "arn:aws:s3:::my-bucket-12345"
bucket_url = "https://my-bucket-12345.s3.amazonaws.com"
sensitive: hiding a value
output "db_password" {value = aws_db_instance.main.password
sensitive = true
}
What this means in practice:
- During
terraform applythe value is shown as(sensitive value). terraform output db_passwordprints the real value (useful for scripts).terraform output -raw db_passworddoes the same.- The state file stores the value in plain text.
sensitivecontrols logging only.
When the value comes from a sensitive variable
If an output references a variable with sensitive = true, that output is also automatically treated as sensitive. Otherwise Terraform exits with an error about exposing a sensitive value in a non-sensitive output.
To output the value explicitly, mark the output as sensitive.
depends_on in an output
This is rare, but sometimes you need to guarantee the creation order of resources that an output references indirectly:
output "endpoint" {value = aws_lb.web.dns_name
depends_on = [aws_lb_listener.https]
}
Why bother? If another project reads this output via remote state, you can guarantee the listener is configured before that project reads the value, rather than getting an empty result.
In most cases depends_on in an output is unnecessary.
Reading outputs from the CLI
# All outputs as JSON
terraform output -json
# One output in human-readable format
terraform output bucket_arn
# "arn:aws:s3:::my-bucket-12345"
# One output without quotes or JSON wrapping, for shell substitution
terraform output -raw bucket_arn
# arn:aws:s3:::my-bucket-12345
# Assign to a bash variable
BUCKET_ARN=$(terraform output -raw bucket_arn)
-raw works only for simple types (string/number). For list or map, use -json + jq.
Outputs between modules
Given a child module:
# modules/vpc/outputs.tf
output "vpc_id" {value = aws_vpc.main.id
}
In the parent HCL:
module "network" {source = "./modules/vpc"
}
resource "aws_subnet" "private" {vpc_id = module.network.vpc_id # ← reference to the module output
}
The prefix is module.<module_name>.<output_name>. This is the only way a child module can return a value to its caller. Internal attributes of the module are not accessible from outside.
Common pitfalls
-
The state file is not encrypted. No matter how many outputs you mark
sensitive = true, values sit interraform.tfstatein plain text. For secrets, encrypt state at the backend level (S3 with SSE-KMS, Terraform Cloud with encryption). -
You cannot reference your own output from elsewhere in HCL. There is no way to use an output value inside the same configuration that defines it. For a reusable value, use tf-locals.
-
When a resource is destroyed, its output disappears. If another project depends on this output via remote state, that project will break. Think before deleting.
-
The output value must be computable. You cannot write
value = "${unknown_var}"ifunknown_varis not defined. Terraform exits with a clear error. -
descriptionis useful. It is documentation for anyone who will use your output, including yourself a year from now. Fill it in. -
-rawon a complex type is an error. Runningterraform output -raw all_tagswhereall_tagsis a map will fail.-rawaccepts string and number only.