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
validationblocks - 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 | |
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
validationblocks - 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 | |
| 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
- Create a new directory for the module
- Define inputs and outputs
- Implement resources inside the module
- Write tests using
terraform test - Document usage in
README.md - 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.
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
| 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 |
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 testcommand 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 | |
| variables.tf | Module input variables | |
| outputs.tf | Module output values | |
| README.md | Documentation for the module | |
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.