Module dependencies in Terraform create challenges when outputs need to pass between modules or when circular references occur. This guide covers diagnosing and fixing module dependency problems.

Understanding Module Dependencies

Terraform automatically infers dependencies from resource references, but sometimes explicit dependencies are needed. Common errors:

bash
Error: Module "vpc" has no output named "subnet_ids"
Error: Cycle detected: module.vpc -> module.security -> module.vpc
Error: Resource 'aws_subnet.public' not found for variable reference

Issue 1: Missing Module Outputs

Referencing outputs that don't exist in the source module.

Error Example: `` Error: Unsupported attribute on main.tf line 25: 25: subnet_ids = module.vpc.subnet_ids This object does not have an attribute named "subnet_ids"

Root Cause:

The module doesn't export the expected output: ``hcl # modules/vpc/main.tf resource "aws_subnet" "public" { count = 3 vpc_id = aws_vpc.main.id cidr_block = var.public_subnet_cidrs[count.index] } # Missing output!

Solution:

Add the missing output to the module: ```hcl # modules/vpc/outputs.tf output "subnet_ids" { description = "List of public subnet IDs" value = aws_subnet.public[*].id }

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

output "subnet_cidr_blocks" { description = "CIDR blocks of the subnets" value = aws_subnet.public[*].cidr_block } ```

Now reference it correctly: ```hcl module "ec2" { source = "./modules/ec2"

subnet_ids = module.vpc.subnet_ids # Now works } ```

Issue 2: Circular Dependencies

Modules that depend on each other create cycles.

Error Example: `` Error: Cycle: module.vpc -> module.security -> module.vpc

Root Cause: ```hcl # Circular dependency module "vpc" { source = "./modules/vpc" sg_ids = module.security.vpc_sg_ids # Needs security group }

module "security" { source = "./modules/security" vpc_id = module.vpc.vpc_id # Needs VPC } ```

Solution:

Restructure to break the cycle: ```hcl # Option 1: Create shared resources at root level resource "aws_vpc" "main" { cidr_block = var.vpc_cidr }

resource "aws_security_group" "vpc_sg" { vpc_id = aws_vpc.main.id }

module "subnets" { source = "./modules/subnets" vpc_id = aws_vpc.main.id }

module "security" { source = "./modules/security" vpc_id = aws_vpc.main.id }

# Option 2: Pass security group ID to VPC module via variable module "vpc" { source = "./modules/vpc" }

module "security" { source = "./modules/security" vpc_id = module.vpc.vpc_id }

# Security groups created after VPC exists # Then update VPC resources that need SG IDs ```

Issue 3: Implicit vs Explicit Dependencies

Resources created in wrong order due to missing dependency declarations.

Error Scenario: `` # Resource A depends on Resource B but Terraform creates A first # This happens when there's no direct reference

Solution:

Use depends_on for explicit dependencies: ```hcl module "network" { source = "./modules/network" }

module "kubernetes" { source = "./modules/kubernetes"

network_id = module.network.vpc_id # Implicit dependency

# Explicit dependency for resources not directly referenced depends_on = [ module.network.nat_gateway, module.network.vpc_endpoint ] } ```

For resources: ```hcl resource "aws_s3_bucket" "logs" { bucket = "application-logs" }

resource "aws_iam_role_policy" "s3_access" { role = aws_iam_role.app.id policy = data.aws_iam_policy_document.s3.json

depends_on = [aws_s3_bucket.logs] } ```

Issue 4: Module Source Changes

Changing module source triggers full replacement.

Error Example: `` Error: Module source has changed Old: ./modules/vpc New: git::https://github.com/org/modules//vpc

Solution:

Properly manage module source changes: ```bash # When changing module sources terraform init -upgrade

# Force reinitialization rm -rf .terraform/modules terraform init ```

Use versioning for remote modules: ```hcl module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 3.0" # Pin version

name = "production" cidr = var.vpc_cidr } ```

Issue 5: Module Output Type Mismatches

Output type doesn't match expected type in consuming module.

Error Example: `` Error: Invalid value for module argument on main.tf line 10: 10: subnet_ids = module.vpc.subnet_ids The given value is not suitable for child module variable "subnet_ids" defined at modules/ec2/variables.tf:5: string required, but got list of strings.

Solution:

Ensure output and variable types match: ```hcl # modules/vpc/outputs.tf output "subnet_ids" { description = "List of subnet IDs" value = aws_subnet.public[*].id # Returns list }

# modules/ec2/variables.tf variable "subnet_ids" { description = "List of subnet IDs" type = list(string) # Must match output type } ```

For complex types: ```hcl # Output complex object output "network_config" { value = { vpc_id = aws_vpc.main.id subnet_ids = aws_subnet.public[*].id cidr_blocks = aws_subnet.public[*].cidr_block } }

# Variable with matching type variable "network_config" { type = object({ vpc_id = string subnet_ids = list(string) cidr_blocks = list(string) }) } ```

Issue 6: For Each with Module Outputs

Using module outputs incorrectly with for_each.

Error Example: `` Error: Invalid for_each argument on main.tf line 15: 15: for_each = module.vpc.subnets The given value is not suitable for "for_each".

Solution:

Ensure module output is a map for for_each: ```hcl # Module output must be a map # modules/vpc/outputs.tf output "subnets" { value = { for i, subnet in aws_subnet.public : "subnet-${i}" => { id = subnet.id cidr_block = subnet.cidr_block az = subnet.availability_zone } } }

# Usage resource "aws_instance" "web" { for_each = module.vpc.subnets

subnet_id = each.value.id # ... }

# Alternative: Convert list to map with tomap() output "subnet_map" { value = tomap({ for subnet in aws_subnet.public : subnet.availability_zone => subnet.id }) } ```

Issue 7: Module Timeouts and Slow Dependencies

Modules with long-running resources cause timeout issues for dependents.

Error Example: `` Error: timeout waiting for state to become 'available'

Solution:

Add explicit timeouts and dependencies: ```hcl resource "aws_db_instance" "main" { # ... configuration ...

timeouts { create = "2h" update = "2h" delete = "2h" } }

resource "aws_instance" "app" { # Wait for database depends_on = [aws_db_instance.main]

# ... configuration ... }

# Or use null_resource for explicit wait resource "null_resource" "wait_for_db" { depends_on = [aws_db_instance.main]

provisioner "local-exec" { command = "sleep 60" # Wait for DB to be fully ready } } ```

Verification Steps

Check module dependencies: ```bash # Visualize dependency graph terraform graph | dot -Tpng > graph.png

# View module outputs terraform output -module=vpc

# List all module resources terraform state list | grep module ```

Test module in isolation: ``bash # Test module independently cd modules/vpc terraform init terraform plan

Best Practices

  1. 1.Define all outputs in a separate outputs.tf file within each module
  2. 2.Use explicit depends_on only when necessary (implicit via references is preferred)
  3. 3.Structure modules to have clear inputs and outputs
  4. 4.Avoid circular dependencies by restructuring resources
  5. 5.Use moved blocks when refactoring module relationships
  6. 6.Document module dependencies in README files