Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

Terraform Output Values

Output values are the way Terraform exposes information after apply — resource IDs, IP addresses, DNS names, ARNs, and any other attribute you want to surface. They serve three key purposes: human visibility after apply, cross-module data sharing, and machine-readable integration with other tools.


Declaring Outputs

outputs.tf
output "vpc_id" {
description = "ID of the created VPC"
value = aws_vpc.main.id
}
output "load_balancer_dns" {
description = "DNS name of the application load balancer"
value = aws_lb.main.dns_name
}
output "public_subnet_ids" {
description = "IDs of all public subnets"
value = aws_subnet.public[*].id # All count-indexed subnet IDs
}
output "private_subnet_ids" {
description = "Map of AZ to private subnet ID"
value = { for k, s in aws_subnet.private : k => s.id }
}

Reading Outputs

After apply, outputs are displayed at the end:

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Outputs:
load_balancer_dns = "my-alb-1234567890.us-east-1.elb.amazonaws.com"
public_subnet_ids = [
"subnet-0abc123",
"subnet-0def456",
"subnet-0ghi789",
]
vpc_id = "vpc-0abc123def456789"

Read outputs after the fact:

Terminal window
# All outputs
terraform output
# Specific output
terraform output vpc_id
# JSON format (for scripting)
terraform output -json
# Raw string (no quotes — pipe to other tools)
terraform output -raw load_balancer_dns
terraform output -raw load_balancer_dns | xargs curl -I

Sensitive Outputs

output "database_connection_string" {
description = "Full database connection string"
value = "postgresql://${aws_db_instance.main.username}:${var.db_password}@${aws_db_instance.main.endpoint}/${aws_db_instance.main.db_name}"
sensitive = true # Hidden in CLI output, accessible via -json or state
}
output "rds_master_password" {
description = "Master password — stored in Secrets Manager"
value = random_password.db.result
sensitive = true
}

Sensitive outputs display as (sensitive value) in the terminal but are accessible programmatically:

Terminal window
terraform output -json database_connection_string # Returns the value in JSON
terraform output -raw database_connection_string # Returns raw string

Outputs from count and for_each Resources

# count-based outputs
resource "aws_instance" "workers" {
count = var.worker_count
instance_type = "t3.micro"
}
output "worker_instance_ids" {
value = aws_instance.workers[*].id # List of all IDs
}
output "worker_private_ips" {
value = aws_instance.workers[*].private_ip
}
# for_each-based outputs
resource "aws_subnet" "private" {
for_each = toset(["a", "b", "c"])
cidr_block = "10.0.${index(["a","b","c"], each.key) + 1}.0/24"
}
output "private_subnet_map" {
description = "Map of AZ suffix to subnet ID"
value = { for k, s in aws_subnet.private : k => s.id }
# Result: { "a" = "subnet-01", "b" = "subnet-02", "c" = "subnet-03" }
}

Cross-Module Output Sharing

This is the primary use case for outputs in modular Terraform. A root module feeds one module’s outputs as another module’s inputs:

# Root module
module "networking" {
source = "./modules/networking"
environment = var.environment
cidr_block = "10.0.0.0/16"
}
module "application" {
source = "./modules/application"
vpc_id = module.networking.vpc_id # Cross-module reference
private_subnet_ids = module.networking.private_subnet_ids
environment = var.environment
}
module "database" {
source = "./modules/database"
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_subnet_ids
app_security_group = module.application.app_security_group_id
}
modules/networking/outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
}
output "private_subnet_ids" {
value = [for s in aws_subnet.private : s.id]
}

Remote State Outputs

When multiple Terraform configurations need to share data, read outputs from another state file:

# networking is managed by a separate Terraform configuration
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "mycompany-terraform-state"
key = "shared/networking/terraform.tfstate"
region = "us-east-1"
}
}
# Use outputs from the networking state
resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.networking.outputs.private_subnet_ids[0]
vpc_security_group_ids = [data.terraform_remote_state.networking.outputs.app_security_group_id]
}

Outputs in CI/CD Scripts

Terminal window
# Get outputs for downstream pipeline steps
VPC_ID=$(terraform output -raw vpc_id)
LB_DNS=$(terraform output -raw load_balancer_dns)
# Wait for load balancer to be available
until curl -sf "http://${LB_DNS}/health"; do
echo "Waiting for load balancer..."
sleep 10
done
# Update DNS record using output
aws route53 change-resource-record-sets \
--hosted-zone-id "$HOSTED_ZONE_ID" \
--change-batch "{
\"Changes\": [{
\"Action\": \"UPSERT\",
\"ResourceRecordSet\": {
\"Name\": \"api.example.com\",
\"Type\": \"CNAME\",
\"TTL\": 300,
\"ResourceRecords\": [{\"Value\": \"$LB_DNS\"}]
}
}]
}"

Computed Outputs

Outputs can contain expressions, not just direct attribute references:

output "app_url" {
description = "Application URL"
value = "https://${aws_lb.main.dns_name}"
}
output "connection_details" {
description = "Connection details for the application team"
value = {
host = aws_db_instance.main.address
port = aws_db_instance.main.port
database = aws_db_instance.main.db_name
username = aws_db_instance.main.username
}
}
output "summary" {
description = "Deployment summary"
value = <<-EOT
Environment: ${var.environment}
Region: ${data.aws_region.current.name}
VPC: ${aws_vpc.main.id}
Load Balancer: ${aws_lb.main.dns_name}
Instances: ${var.instance_count}
EOT
}