Skip to main content

Building Reusable Modules

1. Designing Module Interfaces

Element Description Usage
module Declares a child module to be used in the configuration module "name" { source = "path/to/module" }
input variables Parameters that allow customization of module behavior variable "var_name" { type = string }
output variables Values that are returned from the module for use elsewhere output "output_name" { value = var.value }

Example: Module Interface Design

module "vpc" {
  source = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
  availability_zones = ["us-east-1a", "us-east-1b"]
}

variable "vpc_cidr" {
  type = string
  description = "CIDR block for the VPC"
  default = "10.0.0.0/16"
}

output "vpc_id" {
  value = module.vpc.vpc_id
  description = "ID of the created VPC"
}
Note: Module interfaces should be designed to be as flexible and reusable as possible. Avoid hardcoding values in modules.

Best Practices for Module Interfaces

  • Use descriptive names for variables and outputs
  • Define default values where appropriate
  • Validate input variables with validation blocks
  • Document all inputs and outputs clearly
  • Keep modules focused on a single responsibility

2. Using Variable Validation

Property Description Example
validation Defines validation rules for variables
variable "instance_type" {
  type = string
  validation {
    condition     = contains(["t2.micro", "t2.small", "t2.medium"], var.instance_type)
    error_message = "The instance_type must be one of t2.micro, t2.small, or t2.medium."
  }
}

Example: Validating Input Length

variable "project_name" {
  type = string
  validation {
    condition     = length(var.project_name) >= 3
    error_message = "Project name must be at least 3 characters long."
  }
}

Example: Validating Numeric Range

variable "cpu_count" {
  type = number
  validation {
    condition     = var.cpu_count >= 1 && var.cpu_count <= 64
    error_message = "CPU count must be between 1 and 64."
  }
}
Note: Validation rules are checked during terraform plan and terraform apply.

3. Implementing Module Best Practices

Module Best Practices Summary

  • Use descriptive names and clear documentation
  • Design modules to be reusable and composable
  • Validate inputs with validation blocks
  • Use versioning for module stability
  • Avoid hardcoding values; use variables and outputs
  • Organize code with consistent structure
Best Practice Description Implementation
Descriptive Naming Use clear, consistent names for modules and resources module "vpc" { source = "./modules/vpc" }
Input Validation Validate inputs to prevent invalid configurations
validation {
  name = "valid_cidr"
  condition = length(var.cidr_block) > 0
  error_message = "CIDR block must not be empty"
}
Versioning Pin module versions to ensure stability module "aws_s3" { source = "registry.terraform.io/terraform-aws-modules/s3-bucket/aws" version = "4.0.0" }
Modular Design Keep modules focused on a single responsibility Separate VPC, IAM, and S3 modules

Example: Module with Validation

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  validation {
    condition     = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
    error_message = "Only t3.micro, t.t.small, and t3.medium are allowed."
  }
}
Note: Use module blocks to reference reusable components and avoid duplication.

Module vs. Resource

  • Module: Encapsulates a set of resources and logic
  • Resource: Represents a single infrastructure component

Module Development Workflow

  1. Create a new directory for the module
  2. Define inputs and outputs
  3. Implement resources inside the module
  4. Write tests using terraform test
  5. Document usage in README.md
  6. Version and publish the module

4. Documenting Modules

Example: Module Documentation Structure

# My Terraform Module

This module provides a simple way to create an S3 bucket with specific configurations.

## Requirements

| Name | Version |
|------|---------|
| terraform | >= 1.0 |
| aws | >= 4.0 |

## Providers

| Name | Version |
|------|---------|
| aws | >= 4.0 |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|----------|
| bucket_name | The name of the S3 bucket to create | string | n/a | yes |
| region | AWS region to deploy in | string | "us-east-1" | no |

## Outputs

| Name | Description |
|------|-------------|
| bucket_arn | ARN of the created S3 bucket |
| bucket_id | ID of the created S3 bucket |

## Usage

```hcl
module "my_s3" {
  source = "./modules/s3"
  bucket_name = "my-example-bucket"
  region = "us-west-2"
}
```
Documentation Element Description Best Practice
Module README Primary documentation file for the module Include usage examples, inputs/outputs, and requirements
Module Variables Inputs that the module accepts Use descriptive names and provide default values where appropriate
Module Outputs Values that the module exposes Clearly document what each output represents and how to use it
Usage Examples Code snippets showing how to use the module Provide minimal working examples for common use cases
Note: Use terraform-docs tool to automatically generate documentation from your module's code.
Warning: Keep documentation in sync with actual module code to avoid confusion.

Module Documentation Checklist

5. Using Output Descriptions

Property Description Example
output.name Name of the output variable instance_id
output.description Human-readable description of the output Public IP address of the EC2 instance
output.value Value to be exported from the module aws_instance.example.id

Example: Defining Outputs with Descriptions

output "instance_id" {
  value = aws_instance.example.id
  description = "The ID of the created EC2 instance"
}

output "public_ip" {
  value = aws_instance.example.public_ip
  description = "Public IP address of the EC2 instance"
}
Note: Output descriptions are displayed in the Terraform console when running terraform output or during plan/apply operations.

6. Handling Module Defaults

Property Default Value Description
source ./local-module Path or URL to the module source
version null Version constraint for the module (e.g., "~> 1.0")
providers {} Map of provider aliases to their configurations

Example: Setting Default Provider Configuration

module "aws_instance" {
  source = "./aws-instance-module"
  providers = {
    aws = aws
  }
}
Note: Default values for module inputs can be overridden when calling the module.

7. Implementing Module Testing

Example: Basic Module Test Setup

mkdir -p test/modules/example
touch test/modules/example/main.tf
Test File Description Usage
test/modules/example/main.tf Module under test Defines the module's resources and outputs
test/modules/example/variables.tf Module variables Input values for module configuration
test/modules/example/outputs.tf Module outputs Values exposed by the module

Example: Test Configuration

module "example" {
  source = "./modules/example"
  name   = "test"
}
Note: Terraform's test framework is experimental and requires terraform 1.6+.

Example: Test Case Definition

terraform {
  required_version = ">= 1.6.0"
}

module "example" {
  source = "./modules/example"
  name   = "test"
}

output "result" {
  value = module.example.result
}

Testing Best Practices

  • Use terraform test command to run tests
  • Structure tests in test/ directory
  • Test module inputs and outputs
  • Validate resource creation and configuration

8. Versioning Module Releases

Version Format Description Notes
v1.2.3 Standard semantic versioning format Must start with 'v' prefix
v2.0.0-beta.1 Pre-release version with build metadata Indicates beta or experimental features

Example: Module Version in Registry

module "aws_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "2.17.0"
}
Note: Terraform Registry enforces strict versioning rules for module releases. Versions must follow semantic versioning (SemVer) format.
Warning: Using ">= 2.0.0" in version constraints can lead to unexpected behavior if newer major versions introduce breaking changes.
Version Constraint Meaning Example
~> 2.0.0 Compatible with 2.0.0, allows patch and minor updates ~> 2.0.0 matches 2.0.x
= 2.1.0 Exact version match = 2.1.0 only matches 2.1.0
>= 2.0.0 Minimum version requirement >= 2.0.0 matches 2.0.0 and higher

Best Practices for Module Versioning

  • Use semantic versioning (SemVer) to indicate breaking changes, features, and fixes
  • Tag releases in Git with version tags like v1.0.0
  • Include changelogs in module repositories to document changes between versions
  • Use ~> constraints to allow compatible updates while preventing breaking changes
  • Test modules before publishing new versions

9. Creating Module Examples

Example: Basic Module Structure

my-module/
  ├── main.tf
  ├── variables.tf
  ├── outputs.tf
  └── README.md
File Description Content Example
main.tf Main configuration file for the module
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type
}
variables.tf Module input variables
variable "ami_id" {
  description = "AMI ID for the instance"
  type        = string
}
outputs.tf Module output values
output "instance_id" {
  value = aws_instance.web.id
}
README.md Documentation for the module
# My Module

This module creates an EC2 instance.
Note: Ensure all module files are properly structured and documented for reuse.

10. Publishing to Registry

Field Description Required Example
name Name of the module Yes "my-module"
version Version of the module (must be semantic versioning) Yes "1.0.0"
source Source location of the module (e.g., GitHub, registry) Yes "github.com/organization/my-module"
description Short description of what the module does No "A module to provision AWS S3 buckets"
outputs List of outputs provided by the module No [{ name = "bucket_name", description = "Name of the created bucket" }]
providers Providers required by the module No { aws = ">= 3.0" }

Example: Publishing a module to the Terraform Registry

module "my_module" {
  source  = "registry.terraform.io/organization/my-module"
  version = "1.0.0"

  bucket_name = "my-bucket"
}
Note: Modules must be versioned using semantic versioning (e.g., v1.0.0) to be published to the Terraform Registry.
Warning: Once a module version is published to the registry, it cannot be overwritten. Ensure all changes are tested before publishing.

Was this article helpful?