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:
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.
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:
Instead of searching through a long plan or cloud console, you can expose exactly what matters.
Outputs are how child modules publish values back to the root module or to other modules.
Deployment scripts, Ansible playbooks, Kubernetes bootstrap steps, and CI/CD jobs often depend on values generated by Terraform.
A good output block explains which values a module intentionally exposes.
output BlocksAn 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
}| 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.
output "bucket_name" {
description = "Name of the S3 bucket used for application logs"
value = aws_s3_bucket.logs.id
}output "application_url" {
description = "Base URL for the deployed application"
value = "https://${aws_lb.app.dns_name}"
}output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}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.
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.
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>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:
terraform output CommandAfter you apply a configuration, you can retrieve outputs at any time with the terraform output command.
terraform outputExample:
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.
terraform output alb_dns_nameResult:
app-alb-123456.us-east-1.elb.amazonaws.comThis is convenient in scripts or when you only care about one value.
-json for machine-readable outputterraform output -jsonExample 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.
terraform output application_urlterraform output -jsonA script could then parse the JSON and export variables for later pipeline stages.
Many teams use:
terraform output -json application_urlor 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.
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>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
}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.
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:
For an EC2 module, useful outputs might include:
Outputs define the public contract of the module.
Terraform outputs are frequently used in deployment pipelines.
Imagine a pipeline that:
terraform applyExample workflow:
terraform apply -auto-approve
terraform output application_urlA CI/CD job could use the output value to:
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.
If Terraform provisions an EKS cluster, outputs might provide:
Downstream automation can then configure kubectl, install Helm charts, or update DNS.
Too many outputs make a module noisy. Expose the values callers or operators truly need.
Descriptions make outputs self-documenting and much easier to understand in code reviews.
If an output includes confidential data, set sensitive = true to reduce accidental exposure in logs.
Useful outputs are usually IDs, ARNs, names, URLs, or lists of created resources, not every internal attribute.
Once other code depends on your outputs, changing or removing them can break consumers. Design them intentionally.
Not every resource attribute deserves to be an output. If no one needs it outside the module, leave it internal.
If an output contains credentials or secret tokens, always mark it sensitive.
Remember: hidden in terminal output does not mean removed from state.
Outputs should serve a clear purpose for operators, parent modules, or automation.
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.
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:
If you treat outputs like a clean API for your Terraform code, your configurations will be easier to reuse and easier to automate.
<MCQExercise exercise={{ id: 'tf-outputs-q1', title: 'Question 1: Output Purpose', description: 'What is the main purpose of a Terraform output value?', options: [ { id: 'a', text: 'To define a new provider configuration' }, { id: 'b', text: 'To expose useful values from resources or modules after Terraform runs' }, { id: 'c', text: 'To replace variable blocks entirely' }, { id: 'd', text: 'To lock the state file during apply' }, ], correctId: 'b', explanation: 'Output values are used to publish useful results such as IDs, URLs, IP addresses, or ARNs after Terraform creates or updates infrastructure.', wrongExplanations: { a: 'Provider configuration is handled in provider blocks, not output blocks.', c: 'Variables accept input, while outputs expose results. They solve different problems.', d: 'State locking is handled by the backend, not by outputs.', }, }} />
<MCQExercise exercise={{ id: 'tf-outputs-q2', title: 'Question 2: Sensitive Outputs', description: 'What does sensitive = true do in a Terraform output block?', options: [ { id: 'a', text: 'It encrypts the value inside the state file automatically' }, { id: 'b', text: 'It hides the value from normal terminal output, but the value may still exist in state' }, { id: 'c', text: 'It prevents parent modules from reading the output' }, { id: 'd', text: 'It deletes the value after terraform apply finishes' }, ], correctId: 'b', explanation: 'Sensitive outputs are redacted in normal CLI output, which helps avoid accidental exposure in logs or screenshots. However, the value may still be stored in Terraform state.', wrongExplanations: { a: 'sensitive = true does not automatically encrypt state. Backend security is a separate concern.', c: 'Parent modules can still use the output if the configuration allows it. Sensitive affects display, not module wiring.', d: 'Terraform does not delete the value after apply just because it is marked sensitive.', }, }} />
<MCQExercise exercise={{ id: 'tf-outputs-q3', title: 'Question 3: Module Outputs', description: 'How does a root module usually reference an output named vpc_id from a child module called network?', options: [ { id: 'a', text: 'var.network.vpc_id' }, { id: 'b', text: 'output.network.vpc_id' }, { id: 'c', text: 'module.network.vpc_id' }, { id: 'd', text: 'local.network.vpc_id' }, ], correctId: 'c', explanation: 'Child module outputs are referenced with the module.<module_name>.<output_name> syntax. For a module named network and an output named vpc_id, the correct form is module.network.vpc_id.', wrongExplanations: { a: 'var.<name> is for input variables, not module outputs.', b: 'There is no output.<module> reference pattern in Terraform.', d: 'local.<name> is for local values, not outputs from child modules.', }, }} />
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.
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.
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.
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.
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.
Understand why Terraform keeps state, what is inside it, how remote backends and locking make teamwork safe, and the state commands that rescue you.
Learn what Terraform state is, why terraform.tfstate exists, how it maps to real resources, and why drift, state security and locking matter.
Configure a Terraform remote backend on S3 with DynamoDB state locking, bootstrap it, compare Terraform Cloud and GCS, and migrate state safely.