Module Composition: Passing Data Between Modules
Scenario
You have two modules: one creates an S3 bucket, the other creates an SSM parameter that stores the bucket name. The SSM module needs the bucket name from the S3 module's output. This is module composition — wiring modules together through their inputs and outputs.
Your Objectives
- Create a modules/s3 module that outputs the bucket name.
- Create a modules/config module that accepts a bucket name input and creates an SSM parameter.
- In the root module, pass the S3 module's output as input to the config module.
- Observe the implicit dependency — the config module waits for the S3 module.
Additional Context
Modules communicate only through inputs (variables) and outputs. A parent module passes data by setting module arguments to values from other modules' outputs. Terraform automatically builds the dependency graph — if Module B's input comes from Module A's output, Module A is created first.
modules/s3/main.tf
variable "bucket_name" { type = string }
variable "environment" { type = string; default = "dev" }
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
tags = { Environment = var.environment, ManagedBy = "terraform" }
}
output "bucket_id" { value = aws_s3_bucket.this.id }
output "bucket_arn" { value = aws_s3_bucket.this.arn }modules/config/main.tf
variable "app_name" { type = string }
variable "bucket_name" { type = string } # comes from S3 module output
variable "environment" { type = string; default = "dev" }
resource "aws_ssm_parameter" "bucket_name" {
name = "/${var.app_name}/${var.environment}/bucket-name"
type = "String"
value = var.bucket_name # uses the S3 module's output
tags = { ManagedBy = "terraform" }
}
output "parameter_name" { value = aws_ssm_parameter.bucket_name.name }
output "parameter_arn" { value = aws_ssm_parameter.bucket_name.arn }main.tf (root module)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = "us-east-1" }
data "aws_caller_identity" "current" {}
# Module A: Creates the S3 bucket
module "storage" {
source = "./modules/s3"
bucket_name = "composed-app-${data.aws_caller_identity.current.account_id}"
environment = "dev"
}
# Module B: Creates config that REFERENCES Module A's output
# Terraform automatically knows to create Module A first.
module "config" {
source = "./modules/config"
app_name = "myapp"
bucket_name = module.storage.bucket_id # ← Module A's output → Module B's input
environment = "dev"
}outputs.tf (root module)
output "bucket_name" { value = module.storage.bucket_id }
output "bucket_arn" { value = module.storage.bucket_arn }
output "parameter_name" { value = module.config.parameter_name }
output "parameter_arn" { value = module.config.parameter_arn }Workflow Commands
mkdir -p modules/s3 modules/config
terraform init && terraform apply
# Verify the SSM parameter stores the bucket name
aws ssm get-parameter --name "/myapp/dev/bucket-name" --query 'Parameter.Value'
terraform destroy
✓ Expected Output After Apply
Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: bucket_arn = "arn:aws:s3:::composed-app-123456789012" bucket_name = "composed-app-123456789012" parameter_arn = "arn:aws:ssm:us-east-1:123456789012:parameter/myapp/dev/bucket-name" parameter_name = "/myapp/dev/bucket-name"

