Terraform Outputs: How to with Examples

Terraform outputs expose infrastructure data, enabling cross-module data sharing and integration with external tools for automation and reporting.

Terraform outputs are a fundamental feature for managing and sharing infrastructure information between Terraform deployments. This guide explains what they are, how they're used, and advanced techniques, including integration with Scalr and Terraform Cloud.

What Are Terraform Outputs?

Terraform outputs are named values that are defined by a Terraform configuration. They are similar to return values in programming languages. You define them within your .tf files to make specific pieces of information about your infrastructure easily accessible after terraform apply.

terraform apply

Initializing plugins and modules...
.....
....
...
..
.
module.scalr_dynamic_vpc_dns.aws_subnet.scalr_subnet[3]: Creation complete after 13s [id=subnet-0ec8755c
12736013f]
Apply complete! Resources: 7 added, 0 changed, 7 destroyed.
Outputs:
subnet_cidrs = [
  "10.44.0.0/28",
  "10.44.0.16/28",
  "10.44.0.32/28",
  "10.44.0.48/28",
  "10.44.0.64/28",
  "10.44.0.80/28",
]
subnet_ids = [
  "subnet-0f531658a20a93123",
  "subnet-07375ed5d103c2445",
  "subnet-0f0cd72e3903c0467",
  "subnet-0ec8755c127360668",
  "subnet-0d3435b7be22b5890",
  "subnet-0c8a43bea6a249567",
]
vpc_id = "vpc-057507a43ddb12345"

How Are Outputs Used?

Outputs serve several purposes:

  • Displaying Information: Show important resource attributes (e.g., IP addresses, DNS names, ARNs) on the command line after an apply.
  • Cross-Configuration References: Pass data from one Terraform configuration (and its state file) to another.
  • Integration with Other Tools: Provide data to scripts, CI/CD pipelines, or other automation tools.

Examples of Using Terraform Outputs

VPC ID and Subnet IDs:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
}

output "main_vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnet_id" {
  value = aws_subnet.public.id
}

S3 Bucket Name:

resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-unique-application-bucket-123"
}

output "s3_bucket_name" {
  value       = aws_s3_bucket.my_bucket.id
  description = "The name of the S3 bucket."
}

EC2 Instance Public IP:

resource "aws_instance" "web" {
  ami           = "ami-0abcdef1234567890" # Example AMI
  instance_type = "t2.micro"
  tags = {
    Name = "WebServer"
  }
}

output "web_server_public_ip" {
  value       = aws_instance.web.public_ip
  description = "The public IP address of the web server."
}

After terraform apply, you'll see web_server_public_ip = "X.X.X.X".

Advanced Usage

Maps and Lists: Outputs can return complex data structures.

output "instance_details" {
  value = {
    id        = aws_instance.web.id
    public_ip = aws_instance.web.public_ip
  }
}

output "subnet_ids" {
  value = [aws_subnet.public.id, aws_subnet.private.id]
}

Sensitive Outputs: Mark outputs as sensitive = true to prevent their values from being displayed in plain text in the Terraform CLI or logs. Terraform will still store them in the state file.

output "db_password" {
  value     = aws_db_instance.main.password
  sensitive = true
}

Terraform Output Arguments

The following arguments can be used with outputs:

description (string)

Provides a human-readable explanation of what the output represents. This is highly recommended for documentation and clarity, especially when collaborating on projects.

output "web_server_public_ip" {
  value       = aws_instance.web_server.public_ip
  description = "The public IP address assigned to the main web server instance."
}

sensitive (bool)

When set to true, Terraform will redact the output's value in the CLI output (plan, apply, destroy, terraform output). The value is still stored in the state file, but it won't be printed to the console, making it suitable for passwords, API keys, or other confidential information.

output "database_password" {
  value     = aws_db_instance.main.password
  sensitive = true
  description = "The root password for the main database. This value is sensitive and redacted from logs."
}

depends_on (list of references)

Establishes explicit dependencies between the output and other resources or modules. While Terraform usually infers dependencies automatically based on resource references in the value argument, depends_on can be used in rare cases where an implicit dependency is not sufficient (e.g., if you need to ensure a network rule is applied before trying to connect to an IP exposed by an output, even if the output value itself doesn't directly reference the rule).

resource "aws_instance" "app_server" {
  # ...
}

resource "aws_security_group_rule" "allow_ssh" {
  type        = "ingress"
  from_port   = 22
  to_port     = 22
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
  security_group_id = aws_instance.app_server.vpc_security_group_ids[0]
}

output "app_server_ip" {
  value       = aws_instance.app_server.public_ip
  description = "The public IP of the application server."
  depends_on  = [aws_security_group_rule.allow_ssh] # Ensure SSH rule is active before using IP
}

ephemeral (bool) - Terraform v1.10+ only

Purpose: When true, this argument prevents the output's value from being persisted in the Terraform state file. This is useful for temporary values that are only needed during a specific Terraform run (e.g., for bootstrapping) and should not be permanently recorded in the state.

This argument is primarily intended for child modules. If you set ephemeral = true on a root module output, terraform output might not work as expected because the value isn't stored. Ephemeral outputs can only be referenced in write-only arguments, other child module ephemeral outputs, ephemeral variables, or ephemeral resources.

resource "random_password" "temp_creds" {
  length = 16
}

output "temporary_bootstrap_password" {
  value     = random_password.temp_creds.result
  sensitive = true
  ephemeral = true
  description = "A temporary password generated for bootstrapping. Not stored in state."
}

By understanding and utilizing these arguments, you can make your Terraform outputs more expressive, secure, and tailored to your specific automation and information sharing needs.

Child Module Outputs

You access child module outputs by referencing the module block itself, followed by .outputs and then the name of the output. This allows the parent module to consume values exposed by its nested configurations.

For example, if you have a module named network with an output vpc_id, you would access it in your root module like this:

module "network" {
  source = "./modules/network" # Path to your network module
}

output "main_vpc_id_from_module" {
  value = module.network.outputs.vpc_id
}

Using Outputs from One State File in Another

This is critical for managing complex infrastructure across multiple configurations. The terraform_remote_state data source is your tool for this.

In this scenario, let's imagine you have a network Terraform configuration that creates a VPC and subnets, and an application Terraform configuration that deploys EC2 instances into those subnets.

1. Network Configuration (network/main.tf):

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
}

output "vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnet_id" {
  value = aws_subnet.public.id
}

main.tf

Run terraform apply in the network directory to create the Terraform state file.

2. Application Configuration (application/main.tf):

Now, you want to define the remote state in the application Terraform configuration file. Once defined, you can use the data.terraform_remote_state.network.outputs.public_subnet_id data source to pull the output from the network state file:

data "terraform_remote_state" "network" {
  backend = "local" # Or "s3", "azurerm", etc., depending on your backend
  config = {
    path = "../network/terraform.tfstate" # Path to the network state file
  }
}

resource "aws_instance" "app" {
  ami           = "ami-0abcdef1234567890" # Example AMI
  instance_type = "t2.micro"
  vpc_security_group_ids = [aws_security_group.app.id]
  subnet_id     = data.terraform_remote_state.network.outputs.public_subnet_id
}

resource "aws_security_group" "app" {
  vpc_id = data.terraform_remote_state.network.outputs.vpc_id
  # ... other security group rules
}

main.tf

By using this data source to pull the outputs, you know that you always have the latest outputs.

How to Use Outputs in Scalr and Terraform Cloud

Both Scalr and Terraform Cloud offer native ways to share outputs between workspaces/workspaces without manual terraform_remote_state configuration, simplifying dependency management.

Scalr & Terraform Cloud Shared Output Example:

In both Scalr and Terraform Cloud, the same terraform_remote_state code snippet can be used, but it is done with a slight difference. In Scalr, the hostname, environment, and workspace must be supplied so that the workspace pulling the outputs knows where the source is:

data "terraform_remote_state" "Prod-VPC" {
  backend = "remote"

  config = {
    hostname = "your-account.scalr.io"
    organization = "your-environment"
    workspaces = {
      name = "your-workspace"
    }
  }
}

#locals {
#    subnet_cidrs = data.terraform_remote_state.rs_Prod-VPC.outputs.subnet_cidrs
#    subnet_ids = data.terraform_remote_state.rs_Prod-VPC.outputs.subnet_ids
#    vpc_id = data.terraform_remote_state.rs_Prod-VPC.outputs.vpc_id
#}

In Terraform Cloud, it is very similar, but with some slight differences:

data "terraform_remote_state" "network" {
  backend = "remote"
  config = {
    organization = "your-organization-name"
    workspaces {
      name = "workspace-name"
    }
  }
}

This approach leverages the platform's ability to manage and expose outputs, making cross-workspace dependencies easier.