Fatskills
Practice. Master. Repeat.
Study Guide: TECH **Terraform Outputs & Cross-Module Outputs: Zero-Fluff Study Guide**
Source: https://www.fatskills.com/cloud-application-developer/chapter/tech-terraform-outputs-cross-module-outputs-zero-fluff-study-guide

TECH **Terraform Outputs & Cross-Module Outputs: Zero-Fluff Study Guide**

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~9 min read

Terraform Outputs & Cross-Module Outputs: Zero-Fluff Study Guide

For engineers who need to ship, not just study.


1. What This Is & Why It Matters

Outputs in Terraform are how you expose data from your infrastructure—like an EC2 instance’s public IP, an RDS endpoint, or a VPC ID—so other parts of your code (or other teams) can use it. Cross-module outputs let you pass data between Terraform modules, turning a monolithic script into a reusable, maintainable system.

Why This Matters in Production

  • Without outputs, you’re stuck manually digging through the AWS Console or terraform state to find critical values (e.g., a database URL). This slows down deployments and invites human error.
  • Without cross-module outputs, you end up duplicating code (e.g., defining the same VPC in every module) or hardcoding values (e.g., "vpc-123456"), which breaks when environments change.
  • Real-world scenario: You’re deploying a microservice that needs to connect to a database. The database is defined in a db module, and the microservice is in an app module. Without outputs, you’d have to:
  • Deploy the database.
  • Manually copy the RDS endpoint from the AWS Console.
  • Paste it into the app’s Terraform config.
  • Redeploy the app.
    With outputs, the db module exposes the endpoint, and the app module consumes it automatically. No manual steps. No drift.


2. Core Concepts & Components


1. output Block

  • Definition: A Terraform block that exposes a value from your configuration to the CLI, state file, or other modules.
  • Production insight: Always mark sensitive outputs (like passwords) with sensitive = true to avoid leaking them in logs or the CLI.

2. Output Value

  • Definition: The data you’re exposing (e.g., aws_instance.web.public_ip).
  • Production insight: Use description to document what the output is for (e.g., "The public IP of the web server"). Future you (or your teammates) will thank you.

3. Module Outputs

  • Definition: Outputs defined in a module that can be referenced by the root module or other modules.
  • Production insight: Modules should expose only what’s needed by other modules. Over-exposing outputs creates tight coupling.

4. Root Module Outputs

  • Definition: Outputs defined in the root module (typically main.tf or outputs.tf).
  • Production insight: Root outputs are often used in CI/CD pipelines (e.g., to pass an ALB DNS name to a deployment script).

5. terraform output Command

  • Definition: CLI command to list or query outputs after applying Terraform.
  • Production insight: Use terraform output -json to parse outputs in scripts (e.g., jq for JSON processing).

6. depends_on for Outputs

  • Definition: Ensures Terraform creates resources in the correct order before exposing their outputs.
  • Production insight: Rarely needed, but critical when outputs depend on resources that aren’t directly referenced (e.g., an IAM role that must exist before an EC2 instance can assume it).

7. Remote State Outputs

  • Definition: Using terraform_remote_state to fetch outputs from another Terraform state file (e.g., a shared VPC defined in a separate repo).
  • Production insight: Avoid overusing this—it creates hidden dependencies. Prefer explicit module outputs where possible.

8. sensitive Outputs

  • Definition: Outputs marked with sensitive = true are redacted in CLI output and logs.
  • Production insight: Always mark secrets (e.g., database passwords) as sensitive, but remember they’re still stored in plaintext in the state file. Use a secrets manager (e.g., AWS Secrets Manager) for production.


3. Step-by-Step Hands-On: Deploy a Web App with Cross-Module Outputs


Prerequisites

  • AWS account with admin IAM permissions.
  • Terraform installed (>= 1.0.0).
  • AWS CLI configured (aws configure).

Goal

Deploy a simple web app with: 1. A VPC (defined in a network module).
2. An EC2 instance (defined in a compute module) that uses the VPC’s subnet ID.
3. Output the EC2 instance’s public IP to the CLI.

Step 1: Project Structure

web-app/
├── main.tf          # Root module
├── outputs.tf       # Root outputs
├── modules/
│   ├── network/     # VPC module
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   └── compute/     # EC2 module
│       ├── main.tf
│       ├── outputs.tf
│       └── variables.tf

Step 2: Define the network Module

modules/network/main.tf:


resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  tags = {
Name = "main-vpc" } } resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" tags = {
Name = "public-subnet" } }

modules/network/outputs.tf:


output "vpc_id" {
  description = "The ID of the VPC"
  value       = aws_vpc.main.id
}

output "subnet_id" {
  description = "The ID of the public subnet"
  value       = aws_subnet.public.id
}

Step 3: Define the compute Module

modules/compute/main.tf:


resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 (us-east-1)
  instance_type = "t2.micro"
  subnet_id     = var.subnet_id # Passed from the root module
  tags = {
Name = "web-server" } }

modules/compute/variables.tf:


variable "subnet_id" {
  description = "The subnet ID where the EC2 instance will be launched"
  type        = string
}

modules/compute/outputs.tf:


output "public_ip" {
  description = "The public IP of the EC2 instance"
  value       = aws_instance.web.public_ip
}

Step 4: Define the Root Module

main.tf:


module "network" {
  source = "./modules/network"
}

module "compute" {
  source    = "./modules/compute"
  subnet_id = module.network.subnet_id # Consume the output from the network module
}

outputs.tf:


output "web_server_ip" {
  description = "The public IP of the web server"
  value       = module.compute.public_ip # Consume the output from the compute module
}

Step 5: Deploy and Verify

  1. Initialize Terraform:
    bash
    terraform init
  2. Plan and apply:
    bash
    terraform plan
    terraform apply -auto-approve
  3. Check the output:
    bash
    terraform output web_server_ip

    Example output:
    "54.165.123.45"
  4. Verify the EC2 instance is running:
    bash
    aws ec2 describe-instances --instance-ids $(terraform output -raw web_server_ip | xargs -I {} aws ec2 describe-instances --filters "Name=ip-address,Values={}" --query "Reservations[0].Instances[0].InstanceId" --output text)

4. ? Production-Ready Best Practices


Security

  • Mark sensitive outputs: Always use sensitive = true for secrets (e.g., database passwords).
    hcl output "db_password" {
    value = aws_db_instance.default.password
    sensitive = true }
  • Avoid hardcoding outputs in scripts: Use terraform output -json and parse with jq instead of hardcoding values.
  • Restrict state file access: Outputs are stored in plaintext in the state file. Use remote state with encryption (e.g., S3 + KMS).

Cost Optimization

  • Output only what’s needed: Don’t expose unnecessary values (e.g., don’t output all EC2 attributes if you only need the public IP).
  • Use count or for_each for dynamic outputs: If you have multiple resources (e.g., 10 EC2 instances), output them as a map or list.
    hcl output "instance_ips" {
    value = { for k, v in aws_instance.web : k => v.public_ip } }

Reliability & Maintainability

  • Use description in outputs: Future you (or your teammates) will thank you.
    hcl output "vpc_id" {
    description = "The ID of the VPC (used by compute and database modules)"
    value = aws_vpc.main.id }
  • Prefix output names: Avoid generic names like id or name. Use vpc_id or web_server_ip instead.
  • Document outputs in READMEs: Add a section like: ``` ## Outputs
  • web_server_ip: The public IP of the web server (used by CI/CD to deploy code).
    ```

Observability

  • Log outputs in CI/CD: Use terraform output -json to capture outputs in build logs (e.g., for debugging).
  • Alert on output changes: If an output changes unexpectedly (e.g., a database endpoint), trigger an alert (e.g., Slack notification).


5. ⚠️ Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Forgetting to define an output You can’t reference the value in other modules or scripts. Always define outputs for values other modules or scripts need.
Not using sensitive = true Secrets (e.g., database passwords) appear in logs or CLI output. Mark sensitive outputs with sensitive = true.
Circular dependencies Terraform errors like Error: Cycle: module.network, module.compute. Avoid referencing outputs from Module A in Module B if Module B is used by Module A.
Hardcoding values instead of outputs You manually copy-paste values (e.g., VPC ID) between modules. Always use outputs to pass values between modules.
Over-exposing outputs A module exposes 20 outputs when only 2 are needed. Only expose what’s required by other modules or scripts.
Not handling null outputs Terraform fails if an output is null (e.g., when count = 0). Use try() or coalesce() to handle optional outputs.
```hcl
output "optional_ip" {
value = try(aws_instance.web[0].public_ip, "no-instance")
}
```


6. ? Exam/Certification Focus


Typical Question Patterns

  1. Output syntax: "Which of the following correctly defines an output for an EC2 instance’s public IP?"
  2. output "public_ip" { value = aws_instance.web }
  3. output "public_ip" { value = aws_instance.web.public_ip }

  4. Cross-module outputs: "How do you reference an output from a module named network in another module?"

  5. var.vpc_id
  6. module.network.vpc_id

  7. Sensitive outputs: "How do you prevent a database password from appearing in Terraform logs?"

  8. output "db_password" { value = aws_db_instance.default.password; sensitive = true }

  9. Remote state outputs: "How do you fetch the VPC ID from a remote state file stored in S3?"
    hcl
    data "terraform_remote_state" "network" {
    backend = "s3"
    config = {
    bucket = "my-terraform-state"
    key = "network/terraform.tfstate"
    region = "us-east-1"
    }
    }
    output "vpc_id" {
    value = data.terraform_remote_state.network.outputs.vpc_id
    }

Key ⚠️ Trap Distinctions

  • Outputs vs. variables:
  • Outputs: Expose values from your configuration (e.g., an EC2 IP).
  • Variables: Input values to your configuration (e.g., instance_type = "t2.micro").
  • sensitive vs. encryption:
  • sensitive = true hides the output from logs but does not encrypt it in the state file.
  • For true encryption, use a secrets manager (e.g., AWS Secrets Manager) or remote state with KMS.


7. ? Hands-On Challenge

Challenge: Deploy a Lambda function that needs the ARN of an IAM role defined in a separate iam module. Use outputs to pass the ARN.

Solution: 1. modules/iam/main.tf:
hcl
resource "aws_iam_role" "lambda" {
name = "lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
2. modules/iam/outputs.tf:
hcl
output "lambda_role_arn" {
value = aws_iam_role.lambda.arn
}
3. modules/lambda/main.tf:
hcl
resource "aws_lambda_function" "example" {
function_name = "example-lambda"
role = var.lambda_role_arn
handler = "index.handler"
runtime = "nodejs14.x"
filename = "lambda.zip"
}
4. modules/lambda/variables.tf:
hcl
variable "lambda_role_arn" {
type = string
}
5. Root module:
```hcl
module "iam" {
source = "./modules/iam"
}

module "lambda" {
source = "./modules/lambda"
lambda_role_arn = module.iam.lambda_role_arn
}
```

Why it works: The iam module exposes the role ARN, and the lambda module consumes it via a variable.


8. ? Rapid-Reference Crib Sheet

Command/Concept Example Notes
Define an output output "vpc_id" { value = aws_vpc.main.id } Always add a description.
Reference a module output module.network.subnet_id Use in root module or other modules.
Sensitive output output "db_password" { value = aws_db_instance.default.password; sensitive = true } Hides from logs but not state file.
List all outputs terraform output Use -json for scripting.
Get a specific output terraform output web_server_ip Use -raw to get the value without quotes.
Remote state output data.terraform_remote_state.network.outputs.vpc_id Avoid overuse—creates hidden dependencies.
Dynamic outputs (e.g., with for) output "instance_ips" { value = { for k, v in aws_instance.web : k => v.public_ip } } Useful for multiple resources.
⚠️ Outputs are stored in state terraform state show aws_instance.web State files are plaintext—secure them!
⚠️ Default output format is string terraform output -raw web_server_ip Use -raw to avoid quotes in scripts.


9. ? Where to Go Next

  1. Terraform Outputs Documentation
  2. Terraform Modules Documentation
  3. AWS Terraform Best Practices
  4. Terraform Remote State (for advanced use cases)


ADVERTISEMENT