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
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:
# All outputsterraform output
# Specific outputterraform output vpc_id
# JSON format (for scripting)terraform output -json
# Raw string (no quotes — pipe to other tools)terraform output -raw load_balancer_dnsterraform output -raw load_balancer_dns | xargs curl -ISensitive 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:
terraform output -json database_connection_string # Returns the value in JSONterraform output -raw database_connection_string # Returns raw stringOutputs from count and for_each Resources
# count-based outputsresource "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 outputsresource "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 modulemodule "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}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 configurationdata "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 stateresource "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
# Get outputs for downstream pipeline stepsVPC_ID=$(terraform output -raw vpc_id)LB_DNS=$(terraform output -raw load_balancer_dns)
# Wait for load balancer to be availableuntil curl -sf "http://${LB_DNS}/health"; do echo "Waiting for load balancer..." sleep 10done
# Update DNS record using outputaws 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}