Terraform Output Values
Learn Terraform output values in depth, including output blocks, sensitive outputs, terraform output commands, module outputs, and CI/CD automation patterns.
What Are Output Values in Terraform?
Terraform output values are named results that Terraform prints after it creates or updates infrastructure. They answer a simple but important question: after Terraform finishes, how do you get back the values you care about?
For example, after creating infrastructure you often want to know:
- the public IP of a server
- the DNS name of a load balancer
- the ARN of an IAM role
- the URL of an application
- the ID of a VPC or subnet
Those values usually come from resources created during terraform apply. Output values make them easy to expose in a clean, reusable way.
A basic example looks like this:
output "instance_public_ip" {
description = "Public IP address of the web server"
value = aws_instance.web.public_ip
}
After terraform apply, Terraform can display the result:
instance_public_ip = "54.12.34.56"
Output values are useful for both humans and automation. A person can quickly see important infrastructure details, while a CI/CD script can read outputs and pass them to later steps.
Why Output Values Matter
It is possible to inspect Terraform state manually, but that is not a good workflow. State is verbose, internal, and often sensitive. Output values give you a small curated interface to the most important results.
They help in several ways:
1. Better usability
Instead of searching through a long plan or cloud console, you can expose exactly what matters.
2. Module communication
Outputs are how child modules publish values back to the root module or to other modules.
3. Automation
Deployment scripts, Ansible playbooks, Kubernetes bootstrap steps, and CI/CD jobs often depend on values generated by Terraform.
4. Documentation through code
A good output block explains which values a module intentionally exposes.
Declaring Output Values with output Blocks
An output is declared with an output block:
output "alb_dns_name" {
description = "DNS name of the application load balancer"
value = aws_lb.app.dns_name
}
Common arguments in an output block
| Argument | Purpose |
|---|---|
value | The expression Terraform should expose |
description | Human-readable explanation of the output |
sensitive | Hides the value from normal terminal output |
value is required. description is strongly recommended. sensitive is optional and useful for secrets.
Simple Output Examples
Resource attribute output
output "bucket_name" {
description = "Name of the S3 bucket used for application logs"
value = aws_s3_bucket.logs.id
}
URL output
output "application_url" {
description = "Base URL for the deployed application"
value = "https://${aws_lb.app.dns_name}"
}
List output
output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}
Map output
output "instance_private_ips" {
description = "Private IPs of application instances keyed by name"
value = {
for instance in aws_instance.app :
instance.tags.Name => instance.private_ip
}
}
Outputs can contain simple values or richer data structures, as long as the expression is valid Terraform.
A Realistic Example
Imagine a small AWS deployment that creates a VPC, subnets, and an application load balancer.
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "Public subnet IDs"
value = aws_subnet.public[*].id
}
output "alb_dns_name" {
description = "Public DNS name of the load balancer"
value = aws_lb.app.dns_name
}
output "application_url" {
description = "URL users should open in a browser"
value = "https://${aws_lb.app.dns_name}"
}
This is much more user-friendly than telling people to inspect state or hunt for resource attributes.
Sensitive Outputs
Some outputs should not be displayed openly in the terminal, especially if they contain secrets.
output "db_password" {
description = "Database password"
value = var.db_password
sensitive = true
}
When Terraform prints outputs after apply, a sensitive output is hidden:
db_password = <sensitive>
Very important warning
Sensitive outputs are hidden in terminal output, but they are not removed from Terraform state. If the value is stored in state by Terraform or by the provider, anyone with state access may still be able to retrieve it.
So sensitive = true improves safety in logs and command output, but it is not a substitute for:
- securing remote state
- restricting access to backend storage
- using secret managers where appropriate
Using the terraform output Command
After you apply a configuration, you can retrieve outputs at any time with the terraform output command.
Show all outputs
terraform output
Example:
alb_dns_name = "app-alb-123456.us-east-1.elb.amazonaws.com"
application_url = "https://app-alb-123456.us-east-1.elb.amazonaws.com"
vpc_id = "vpc-0abc123def456"
This is useful when you want a quick summary of the deployment.
Show a specific output
terraform output alb_dns_name
Result:
app-alb-123456.us-east-1.elb.amazonaws.com
This is convenient in scripts or when you only care about one value.
Use -json for machine-readable output
terraform output -json
Example shape:
{
"alb_dns_name": {
"sensitive": false,
"type": "string",
"value": "app-alb-123456.us-east-1.elb.amazonaws.com"
},
"vpc_id": {
"sensitive": false,
"type": "string",
"value": "vpc-0abc123def456"
}
}
JSON output is the preferred format for automation because scripts can parse it reliably.
Output command examples in practice
Fetch a load balancer URL for testing
terraform output application_url
Save all outputs for another tool
terraform output -json
A script could then parse the JSON and export variables for later pipeline stages.
Read a single output cleanly in shell automation
Many teams use:
terraform output -json application_url
or parse the full JSON with tools available in their environment. The key lesson is that Terraform can provide machine-readable output, which is much better than scraping human-oriented terminal text.
Passing Outputs Between Modules
Outputs are essential in Terraform module design.
A child module exposes values using output blocks, and the parent module reads them with:
module.<module_name>.<output_name>
Child module example
Imagine a module that creates a VPC.
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID of the created VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "IDs of the public subnets"
value = aws_subnet.public[*].id
}
Root module using those outputs
module "network" {
source = "./modules/vpc"
project_name = var.project_name
environment = var.environment
vpc_cidr = var.vpc_cidr
}
module "app" {
source = "./modules/app"
vpc_id = module.network.vpc_id
subnet_ids = module.network.public_subnet_ids
}
This is how Terraform modules communicate safely and intentionally.
Why outputs matter in module design
A good module does not expose every possible internal detail. It exposes the values that callers actually need.
For a VPC module, useful outputs might include:
- VPC ID
- public subnet IDs
- private subnet IDs
- route table IDs
For an EC2 module, useful outputs might include:
- instance ID
- private IP
- public IP
- security group ID
Outputs define the public contract of the module.
Using Outputs in CI/CD Scripts
Terraform outputs are frequently used in deployment pipelines.
Imagine a pipeline that:
- runs
terraform apply - reads the application URL
- runs smoke tests against the deployed service
Example workflow:
terraform apply -auto-approve
terraform output application_url
A CI/CD job could use the output value to:
- run a health check
- create a deployment annotation
- pass the URL to integration tests
- store the endpoint as a pipeline variable
Example scenario: DNS or app endpoint
Suppose Terraform creates an Application Load Balancer and your test step needs its hostname.
output "application_url" {
description = "Public endpoint for smoke tests"
value = "https://${aws_lb.app.dns_name}"
}
Now your pipeline can read this output and test the environment automatically.
Example scenario: Kubernetes bootstrap
If Terraform provisions an EKS cluster, outputs might provide:
- cluster name
- cluster endpoint
- node security group ID
- OIDC provider ARN
Downstream automation can then configure kubectl, install Helm charts, or update DNS.
Best Practices for Output Values
1. Output what is useful, not everything
Too many outputs make a module noisy. Expose the values callers or operators truly need.
2. Always add descriptions
Descriptions make outputs self-documenting and much easier to understand in code reviews.
3. Mark secrets as sensitive
If an output includes confidential data, set sensitive = true to reduce accidental exposure in logs.
4. Prefer stable, meaningful outputs
Useful outputs are usually IDs, ARNs, names, URLs, or lists of created resources, not every internal attribute.
5. Think of outputs as a module API
Once other code depends on your outputs, changing or removing them can break consumers. Design them intentionally.
Common Beginner Mistakes
Outputting values that should stay internal
Not every resource attribute deserves to be an output. If no one needs it outside the module, leave it internal.
Forgetting to mark secrets as sensitive
If an output contains credentials or secret tokens, always mark it sensitive.
Assuming sensitive outputs are fully secure
Remember: hidden in terminal output does not mean removed from state.
Creating outputs but never using them
Outputs should serve a clear purpose for operators, parent modules, or automation.
A Full Example: Outputs for a Web Stack
output "vpc_id" {
description = "ID of the VPC used by the application"
value = aws_vpc.main.id
}
output "alb_dns_name" {
description = "DNS name of the application load balancer"
value = aws_lb.app.dns_name
}
output "application_url" {
description = "Public HTTPS URL for the application"
value = "https://${aws_lb.app.dns_name}"
}
output "app_security_group_id" {
description = "Security group ID attached to application instances"
value = aws_security_group.app.id
}
output "private_instance_ips" {
description = "Private IP addresses of app servers"
value = aws_instance.app[*].private_ip
}
This gives both humans and automation a concise summary of the deployment.
Final Thoughts
Output values are one of Terraform's simplest features, but they are incredibly useful. They turn raw infrastructure details into an intentional interface. For beginners, they are the easiest way to answer questions like, "What did Terraform just create for me?" For teams, they are the glue that connects modules and automation.
A practical summary is:
- Resources create infrastructure
- Outputs expose important results
- Modules publish outputs to parents
- CI/CD pipelines consume outputs for automation
- Sensitive outputs hide terminal display but not state contents
If you treat outputs like a clean API for your Terraform code, your configurations will be easier to reuse and easier to automate.
Knowledge Check
Question 1: Output Purpose
What is the main purpose of a Terraform output value?
Question 2: Sensitive Outputs
What does sensitive = true do in a Terraform output block?
Question 3: Module Outputs
How does a root module usually reference an output named vpc_id from a child module called network?
Frequently Asked Questions
What are Terraform outputs used for?
They expose values from a configuration, an IP address, a DNS name, a database endpoint, a generated ARN, so that they can be read after apply, consumed by a calling module, or picked up by a CI script. Within a module, outputs are the only way to return anything to the caller: a parent module cannot reach inside a child to read a resource attribute directly. Outputs are also how humans find the URL of what they just deployed.
How do I read an output value?
terraform output prints them all, terraform output name prints one, and terraform output -json produces machine-readable output for scripts to parse with jq. The -raw flag prints a single string value without quotes, which is what you want when interpolating into a shell command. Outputs are read from state, so they are available any time after apply without re-running anything.
What does marking an output sensitive actually do?
It redacts the value in terraform plan and terraform apply console output, showing (sensitive value) instead. That is all. The value is still stored in plain text in the state file, still returned by terraform output -json, and still visible to anyone who can read state. It protects against a secret appearing in a CI log or a screenshot. It is not encryption, and it is not an access control.
Why does my output show "known after apply" in the plan?
Because it depends on an attribute that does not exist until the resource is created: an auto-generated ID, an assigned IP address, an ARN. Terraform cannot know it at plan time, so it shows a placeholder. This is normal and requires no action. If an output you expected to be static shows this, it is referencing a resource attribute rather than a variable or literal.
How do I pass outputs between modules?
A child module declares an output, and the parent references it as module.<name>.<output>. That value can then be passed as an input to another module, which is how you wire a network module's subnet IDs into a compute module. For passing values between separate root configurations with separate state, use a terraform_remote_state data source, though many teams prefer writing the value to SSM Parameter Store and reading it back, which avoids coupling one configuration's state to another's.
Can I use outputs in CI/CD pipelines?
Yes, and it is the standard way to hand values to later steps. Run terraform output -json > outputs.json and parse it, or use -raw to capture a single value into a shell variable. Typical uses are passing a load balancer DNS name to a smoke test, an ECR repository URL to a build step, or a cluster name to a deployment job. Remember sensitive outputs appear unredacted in -json, so avoid printing the whole document into a log.