Terraform Module Outputs
Introduction
When working with Terraform modules, you often need to share information between modules or expose certain values to the root module. This is where module outputs come into play. Outputs allow a module to export specific values that can be used by the calling module or displayed after applying your Terraform configuration.
Think of outputs as the return values of a function in programming. Just as functions can return values that can be used elsewhere in your code, Terraform modules can output values that can be referenced by other parts of your infrastructure configuration.
Understanding Module Outputs
What Are Module Outputs?
In Terraform, outputs serve three main purposes:
- Exposing data for the CLI: When you run
terraform apply
, outputs are displayed in your terminal - Providing data to parent modules: Child modules can share information with their parent modules
- Storing values in the state file: Outputs are stored in the Terraform state for reference by other configurations
Basic Output Syntax
Here's the basic syntax for defining outputs in a module:
output "output_name" {
value = expression
description = "Description of the output"
sensitive = false
}
Let's break down the components:
output_name
: A unique identifier for the outputvalue
: The value to be exported (can be any Terraform expression)description
: A human-readable description of the output (optional but recommended)sensitive
: Boolean that marks output as sensitive to hide it from normal output (optional, defaults tofalse
)
Creating Module Outputs
Let's create a simple module that provisions an AWS S3 bucket and outputs important information about it.
Example Module Structure
modules/
└── s3_bucket/
├── main.tf
├── variables.tf
└── outputs.tf
main.tf
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
tags = {
Name = var.bucket_name
Environment = var.environment
}
}
resource "aws_s3_bucket_acl" "this" {
bucket = aws_s3_bucket.this.id
acl = var.acl
}
variables.tf
variable "bucket_name" {
description = "Name of the S3 bucket"
type = string
}
variable "environment" {
description = "Deployment environment"
type = string
default = "dev"
}
variable "acl" {
description = "ACL for the S3 bucket"
type = string
default = "private"
}
outputs.tf
output "bucket_id" {
description = "The ID of the S3 bucket"
value = aws_s3_bucket.this.id
}
output "bucket_arn" {
description = "The ARN of the S3 bucket"
value = aws_s3_bucket.this.arn
}
output "bucket_domain_name" {
description = "The domain name of the bucket"
value = aws_s3_bucket.this.bucket_domain_name
}
output "bucket_regional_domain_name" {
description = "The regional domain name of the bucket"
value = aws_s3_bucket.this.bucket_regional_domain_name
}
Using Module Outputs
Now that we have defined our outputs, let's see how to use them in your root module.
Root Module Configuration
module "website_bucket" {
source = "./modules/s3_bucket"
bucket_name = "my-website-assets"
environment = "production"
acl = "public-read"
}
resource "aws_cloudfront_distribution" "website_cdn" {
origin {
domain_name = module.website_bucket.bucket_regional_domain_name
origin_id = module.website_bucket.bucket_id
}
# Other CloudFront configuration...
}
output "website_url" {
description = "The URL of the website"
value = "https://${aws_cloudfront_distribution.website_cdn.domain_name}"
}
Accessing Module Outputs
To access an output from a module, use the syntax module.<MODULE_NAME>.<OUTPUT_NAME>
. In our example above, we access the bucket's regional domain name with module.website_bucket.bucket_regional_domain_name
.
Chaining Module Outputs
Outputs can be chained through multiple levels of modules. This is particularly useful in complex infrastructure setups.
Root Module
├── Module A (outputs X, Y)
└── Module B (uses Module A's output X)
└── Module C (uses Module B's outputs)
Let's see an example of this in practice:
Module A (database)
# In modules/database/outputs.tf
output "db_endpoint" {
value = aws_db_instance.main.endpoint
}
output "db_name" {
value = aws_db_instance.main.name
}
Module B (application)
# In modules/application/main.tf
module "database" {
source = "../database"
# variables...
}
# In modules/application/outputs.tf
output "app_url" {
value = aws_elastic_beanstalk_environment.app.cname
}
output "database_endpoint" {
value = module.database.db_endpoint
}
Root Module
module "app" {
source = "./modules/application"
# variables...
}
output "application_url" {
value = module.app.app_url
}
output "database_connection_string" {
value = "postgresql://admin:password@${module.app.database_endpoint}/app"
sensitive = true
}
Advanced Output Features
Sensitive Outputs
Sometimes outputs contain sensitive information like passwords or tokens. Terraform allows you to mark these as sensitive:
output "database_password" {
description = "The password for the database"
value = aws_db_instance.db.password
sensitive = true
}
When an output is marked as sensitive:
- It won't be displayed in the CLI output of
terraform apply
orterraform output
- It can still be accessed programmatically or when explicitly requested
Transforming Output Values
You can transform values before outputting them using Terraform functions:
output "s3_bucket_urls" {
description = "List of URLs for the S3 buckets"
value = [for bucket in aws_s3_bucket.buckets : "https://${bucket.bucket_domain_name}"]
}
output "environment_mapping" {
description = "Map of environments to their S3 buckets"
value = {
for bucket in aws_s3_bucket.multi_env :
bucket.tags.Environment => bucket.id
}
}
Conditional Outputs
You can conditionally include outputs based on variables or other conditions:
output "load_balancer_ip" {
description = "The IP address of the load balancer"
value = var.create_load_balancer ? aws_lb.main[0].dns_name : null
}
Practical Use Cases
1. Exposing Infrastructure Information
A common use case is to expose information about provisioned infrastructure:
# Module that creates a VPC
module "vpc" {
source = "./modules/vpc"
# variables...
}
# Outputs for easy reference
output "vpc_id" {
value = module.vpc.vpc_id
}
output "public_subnets" {
value = module.vpc.public_subnet_ids
}
output "private_subnets" {
value = module.vpc.private_subnet_ids
}
2. Multi-Environment Configuration
Outputs can help manage multi-environment deployments:
module "network" {
source = "./modules/network"
environment = var.environment
}
module "database" {
source = "./modules/database"
subnet_ids = module.network.private_subnet_ids
environment = var.environment
}
module "application" {
source = "./modules/application"
vpc_id = module.network.vpc_id
subnet_ids = module.network.public_subnet_ids
db_endpoint = module.database.endpoint
environment = var.environment
}
output "application_url" {
value = module.application.url
}
3. Creating Dynamic Configuration Files
Outputs can be used with the local_file
resource to generate configuration files:
module "database" {
source = "./modules/database"
# variables...
}
module "redis" {
source = "./modules/redis"
# variables...
}
resource "local_file" "app_config" {
content = templatefile("${path.module}/templates/config.yml.tpl", {
db_host = module.database.endpoint
db_name = module.database.name
redis_endpoint = module.redis.endpoint
})
filename = "${path.module}/config.yml"
}
Visualizing Module Outputs
Here's a diagram showing how module outputs flow through a Terraform configuration:
Best Practices for Module Outputs
- Name outputs clearly: Use descriptive names that indicate what the output contains
- Always add descriptions: Include helpful descriptions for all outputs
- Mark sensitive data: Use the
sensitive
attribute for passwords, tokens, etc. - Output what's needed: Only output values that will be used by parent modules or need to be displayed
- Keep outputs consistent: Use consistent naming conventions across modules
- Document outputs: Include output documentation in module README files
Summary
Module outputs are a powerful feature in Terraform that enable:
- Sharing information between modules
- Creating flexible, reusable infrastructure components
- Exposing important information after deployment
- Building complex module hierarchies
By properly using outputs, you can create modular, maintainable Terraform configurations that are easy to understand and extend.
Further Exercises
- Create a module that provisions an AWS EC2 instance and outputs its IP address and instance ID
- Build a multi-module setup with network, database, and application layers that share information via outputs
- Create a module that outputs different values based on an input variable
- Use the
terraform output
command to extract values from your Terraform state and integrate them with a CI/CD pipeline
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)