Skip to main content

Managing Module Composition Patterns

1. Using Root Modules

Property Description Example
source Path to the module source ./modules/web-server
version Version of the module to use v1.2.0
providers Map of provider aliases to provider configurations { aws = aws }

Example: Root Module Configuration

module "web_server" {
  source  = "./modules/web-server"
  version = "v1.2.0"

  instance_type = "t3.micro"
  ami_id        = "ami-12345678"

  providers = {
    aws = aws
  }
}
Note: Root modules are the entry point for Terraform configurations and can reference other modules to compose complex infrastructure.

2. Creating Child Modules

Property Description Example
source Path to the module source code ./child-module
version Version of the module to use v1.0.0
providers Map of provider aliases to provider configurations { aws = aws }

Example: Creating a child module

module "web_server" {
  source = "./web-server-module"
  instance_type = "t3.micro"
  ami_id = "ami-12345678"
}
Note: Child modules are defined in the module block and can be reused across different configurations.

3. Implementing Module Nesting

Module nesting in Terraform allows you to organize and reuse configurations by embedding modules within other modules. This pattern promotes modularity, reusability, and maintainability of infrastructure code.

Property Description Example
source Specifies the source of the module to be used. source = "terraform-aws-modules/vpc/aws"
version Defines the version of the module to use (optional but recommended). version = "3.0.0"
variables Variables passed to the nested module. cidr_block = "10.0.0.0/16"

Example: Using Nested Modules

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "3.0.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets = ["10.0.10.0/24", "10.0.11.0/24", "10.0.12.0/24"]
}

module "web_server" {
  source = "./modules/web-server"
  vpc_id = module.vpc.vpc_id
  subnet_id = module.vpc.private_subnets[0]
}
Note: When nesting modules, ensure that variables and outputs are properly defined to allow communication between parent and child modules.

4. Using Module Dependencies

Property Description Example
source Location of the module (local path, Git repo, registry) ./modules/web-server
version Version constraint for registry modules ~> 1.0
providers Mapping of module providers to local aliases { aws = aws }

Example: Module with Dependencies

module "web_server" {
  source = "./modules/web-server"
  count = 2
  vpc_id = module.vpc.vpc_id
  instance_type = "t3.micro"
}

Example: Registry Module with Version

module "alb" {
  source = "terraform-aws-modules/alb/aws"
  version = "~> 7.0"
  vpc_id = module.vpc.vpc_id
  subnets = module.vpc.public_subnets
}
Note: Module dependencies are resolved at terraform init time. Changes to dependencies require re-running terraform init.

5. Sharing Data Between Modules

Source Data Type Description
output string Value exported from a module
module map Map of module outputs

Example: Referencing Module Outputs

module "web_server" {
  source = "./web_server"
}

module "database" {
  source = "./database"
  db_name = module.web_server.db_name
}

Example: Using Output Values in Variables

variable "web_server_port" {
  description = "Port for web server"
  default = module.web_server.port
}
Note: Outputs must be declared in the module that defines them and referenced in the consuming module.
Warning: Avoid circular dependencies between modules when sharing data.

6. Implementing Module Abstraction

Pattern Description Usage
Input Variables Define inputs to modules using variable blocks.
variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}
Output Variables Expose values from a module using output blocks.
output "instance_id" {
  value = aws_instance.example.id
}
Module Nesting Use modules to compose complex infrastructure.
module "vpc" {
  source = "./vpc_module"
}

Example: Abstracting EC2 Instance Creation

module "web_instance" {
  source = "./ec2_module"
  instance_type = "t3.micro"
  ami_id        = "ami-12345678"
}
Note: Abstraction allows reusability and simplifies complex configurations.

7. Creating Module Wrappers

Example: Basic Module Wrapper

module "wrapper" {
  source = "./my-module"
  version = "1.0.0"

  # Pass through variables
  instance_type = var.instance_type
  vpc_id = var.vpc_id
}
Property Description Usage
source Path or URL to the wrapped module Required for module reference
version Version constraint for the wrapped module Optional, but recommended
depends_on Explicit dependency declaration Optional, for complex dependencies
Note: Module wrappers provide a stable interface for consumers while allowing internal module changes.

Wrapper vs Direct Module Usage

  • Wrapper: Provides abstraction layer, version control, and parameter normalization
  • Direct: Simpler but less flexible for future changes

8. Using Module Factory Patterns

Module factory patterns in Terraform allow you to create reusable, parameterized modules that can generate multiple instances of similar infrastructure with different configurations.

Pattern Description Use Case
factory module A module that accepts a list of configurations and creates multiple instances of a resource or set of resources Creating multiple similar resources with different parameters

Example: Factory Module for EC2 Instances

module "ec2_factory" {
  source = "./modules/ec2-factory"
  instances = [
    {
      name        = "web-server-1"
      instance_type = "t3.micro"
      ami         = "ami-12345678"
    },
    {
      name        = "web-server-2"
      instance_type = "t3.small"
      ami         = "ami-87654321"
    }
  ]
}

Example: Factory Module Implementation

# modules/ec2-factory/main.tf
resource "aws_instance" "instance" {
  count = length(var.instances)
  ami           = var.instances[count.index].ami
  instance_type = var.instances[count.index].instance_type
  tags = {
    Name = var.instances[count.index].name
  }
}
Note: The factory pattern is particularly useful for managing infrastructure that follows a similar structure but requires different configurations, such as multiple web servers or database instances with varying sizes.

9. Implementing Module Versioning Strategies

Strategy Description Usage Notes
Semantic Versioning Follows MAJOR.MINOR.PATCH format (e.g., 1.2.3) version = "1.2.3" Recommended for stable modules
Git Tags Use Git tags to mark module versions (e.g., v1.0.0) source = "github.com/owner/repo?ref=v1.0.0" Supports any Git-compatible source
Branch References Point to specific branches (e.g., main, develop) source = "github.com/owner/repo?ref=main" Not recommended for production due to instability
Registry Versioning Use Terraform Registry for versioned modules source = "registry.terraform.io/owner/module" Enables version pinning and discovery

Example: Using Registry Module with Version Pinning

module "vpc" {
  source  = "registry.terraform.io/terraform-aws-modules/vpc/aws"
  version = "3.14.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"
}

Example: Using Git Tag for Module Version

module "alb" {
  source = "github.com/terraform-aws-modules/terraform-aws-alb?ref=v2.15.0"
  name   = "my-alb"
}
Note: Always pin module versions in production to ensure reproducibility and prevent unexpected changes.
Warning: Using ref=main or other branch names can lead to breaking changes if the module is updated without bumping the version.

10. Managing Cross-Module State

Property Description Usage
module.<name>.outputs References outputs from a child module
output "instance_id" {
  value = module.ec2_instance.id
}
data.<type>.<name> Fetches external data for use in configuration
data "aws_ami" "ubuntu" {
  most_recent = true
  filter {
    name   = "name"
    values = ["ubuntu/images/*"]
  }
}

Example: Using Module Outputs

module "vpc" {
  source = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
}

module "web" {
  source = "./modules/web"
  vpc_id = module.vpc.vpc_id
}
Note: Terraform does not allow circular dependencies between modules. Ensure that module outputs are used in a way that avoids cycles.
Warning: Avoid tightly coupling modules by referencing outputs directly. Prefer passing inputs to modules rather than relying on internal state.

Was this article helpful?