Working with Modules
1. Creating Module Structure
Modules in Terraform are self-contained packages of infrastructure that can be reused across different configurations. A well-structured module includes specific directory layouts and files to define its behavior.
| File/Directory | Description | Required? |
|---|---|---|
| main.tf | Contains the core configuration for the module, including resources, data sources, and outputs. | Yes |
| variables.tf | Defines input variables that allow customization of the module's behavior. | No |
| outputs.tf | Declares output values that can be consumed by other modules or configurations. | No |
| versions.tf | Specifies the minimum Terraform version required for the module. | No |
| README.md | Documentation for the module, including usage instructions and examples. | No |
| examples/ | Directory containing example configurations that demonstrate how to use the module. | No |
| tests/ | Directory for automated tests (e.g., using Terratest) to validate module functionality. | No |
Example: Basic Module Structure
my-module/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
└── README.md
Example: Module with Examples and Tests
my-module/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── README.md
├── examples/
│ └── basic/
│ ├── main.tf
│ └── variables.tf
└── tests/
└── test.tf
2. Calling Modules
| Property | Description | Example |
|---|---|---|
| source | Path to the module source | ./modules/web-server |
| version | Version constraint for the module | ~> 1.0.0 |
| providers | Map of provider aliases to module providers | { aws = aws } |
Example: Basic Module Call
module "web_server" {
source = "./modules/web-server"
instance_type = "t3.micro"
ami_id = "ami-12345678"
}
Example: Module with Version
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 3.0"
name = "my-vpc"
}
Note: When using a local path as the source, Terraform will look for the module in that directory relative to the current working directory.
3. Passing Input Variables
| Variable | Type | Description | Default |
|---|---|---|---|
| source | string |
Path to the module directory or Git repository | . |
| version | string |
Version constraint for the module (e.g., "~> 1.0") |
null |
Example: Using a local module with input variables
module "web_server" {
source = "./modules/web-server"
instance_type = "t3.micro"
ami_id = "ami-12345678"
}
Example: Using a remote module with version constraint
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 3.0"
cidr = "10.0.0.0/16"
}
4. Setting Module Sources
| Source Type | Format | Description | Example |
|---|---|---|---|
| Local Path | ./modules/my-module |
Module stored locally in the project directory | |
| Git Repository | github.com/username/repo//module-path |
Module stored in a Git repository (e.g., GitHub, GitLab) | |
| Registry | registry.terraform.io/username/module-name |
Module from the Terraform Registry (public or private) | |
| HTTP/HTTPS | https://example.com/module.zip |
Module provided as a ZIP archive via HTTP/HTTPS | |
| S3 Bucket | s3::https://s3.amazonaws.com/bucket-name/module.zip |
Module stored in an S3-compatible storage bucket | |
Example: Using a Local Module Source
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
}
Example: Using a Git Module Source
module "alb" {
source = "github.com/terraform-aws-modules/terraform-aws-alb//v2.0"
name = "my-alb"
}
Note: When using Git sources, you can specify a version tag or branch using
ref like source = "github.com/username/repo//module-path?ref=v1.0".
5. Versioning Modules
| Version Type | Description | Usage | Notes |
|---|---|---|---|
| Module Version | Represents a specific release of a module | source = "registry.terraform.io/hashicorp/aws" |
Must follow semantic versioning (SemVer) format |
| Git Ref | Points to a specific commit, tag, or branch in a Git repository | source = "git::https://example.com/terraform-modules.git//module1?ref=v1.0.0" |
Use tags for stable releases, branches for development |
| Local Path | Points to a local directory containing the module | source = "./modules/ec2-instance" |
Useful for development and testing |
Example: Using Module Version
module "aws_instance" {
source = "registry.terraform.io/hashicorp/aws"
version = "5.0.0"
instance_type = "t3.micro"
}
Example: Using Git Ref
module "custom_module" {
source = "git::https://github.com/example/terraform-modules.git//networking?ref=release-v2.1"
}
Note: When using local paths, changes to the module files will be reflected immediately in the Terraform configuration.
Warning: Always pin module versions in production to ensure reproducibility and stability.
Best Practices for Module Versioning
- Use semantic versioning (e.g.,
v1.2.3) for registry modules - Tag releases in Git repositories to ensure reproducibility
- Avoid using
masterormainbranches in production configurations - Use
terraform initto update module versions when needed
6. Accessing Module Outputs
| Output Name | Type | Description | Example Usage |
|---|---|---|---|
| aws_instance.id | string |
ID of the created EC2 instance | |
| vpc_cidr_block | string |
CIDR block of the VPC | |
| public_ips | list(string) |
List of public IP addresses of instances | |
Example: Using Module Outputs in Parent Configuration
module "web_server" {
source = "./web_server_module"
instance_type = "t3.micro"
}
resource "aws_lb" {
name = "web-lb"
target_group_arns = [module.web_server.target_group_arn]
}
Note: Outputs must be defined in the module's
outputs.tf file and referenced using the module.<module_name>.<output_name> syntax in the parent configuration.7. Using Module Dependencies
| Dependency Type | Description | Usage | Notes |
|---|---|---|---|
| module | Declares a dependency on another module | |
Source can be local path, Git repo, or registry |
| version | Specifies the version of a module | |
Ensures consistent behavior across environments |
| providers | Maps module providers to local provider aliases | |
Useful for multi-region or multi-provider setups |
Example: Module with Version Constraint
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = ">= 3.0.0, < 4.0.0"
bucket = "my-unique-bucket-name"
acl = "private"
}
Note: Module dependencies are resolved during
terraform init. Ensure all dependencies are accessible before running this command.
Warning: Avoid circular dependencies between modules. Terraform will fail with an error if it detects a cycle.
8. Publishing Modules
| Step | Description | Action |
|---|---|---|
| 1 | Prepare module code | Ensure all files are organized and documented |
| 2 | Create a GitHub/GitLab repository | Initialize with a README and LICENSE |
| 3 | Version your module | Use semantic versioning (e.g., v1.0.0) |
| 4 | Tag the release | Push a Git tag matching the version |
| 5 | Upload to registry | Use Terraform Cloud or Registry to publish |
Example: Publishing to Terraform Registry
git init
git add .
git commit -m "Initial commit"
git tag v1.0.0
git push origin main --tags
Note: Ensure the module is compatible with the latest Terraform version and includes proper documentation.
Warning: Avoid publishing modules with sensitive data or secrets.
9. Using Private Module Registry
| Field | Description | Example |
|---|---|---|
| source | Module source path, including registry URL | registry.terraform.io/hashicorp/aws |
| version | Version constraint for the module | >= 4.0.0, < 5.0.0 |
Example: Using Private Module Registry
module "vpc" {
source = "registry.terraform.io/hashicorp/aws"
version = ">= 4.0.0, < 5.0.0"
vpc_cidr = "10.0.0.0/16"
}
Note: Private module registries require authentication via
terraform login or environment variables.
Warning: Ensure the registry URL matches your private registry's endpoint.
| Registry Type | Authentication Method | Notes |
|---|---|---|
registry.terraform.io |
Token via terraform login |
Public registry, requires token for private modules |
my-private-registry.com |
Token or API key in ~/.terraformrc |
Self-hosted or third-party private registry |
10. Managing Module Composition
| Property | Description | Example |
|---|---|---|
| source | Location of the module, can be local path, Git repo, or registry | source = "terraform-aws-modules/vpc/aws" |
| version | Version constraint for the module | version = "~> 3.0" |
| providers | Map of provider aliases to module providers | providers = { aws = aws.east } |
Example: Using a Module with Version Constraint
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 3.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"]
}
Note: Module composition allows for reusability and abstraction of infrastructure components. Ensure version constraints are set to avoid breaking changes in your infrastructure.
Best Practices for Module Composition
- Use version constraints to maintain stability and prevent unexpected changes.
- Organize modules in a hierarchical structure for better manageability.
- Use provider aliases to handle multiple providers with the same type but different configurations.
11. Refactoring with Modules
Example: Migrating to Modules
// Before: Monolithic configuration
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
resource "aws_instance" "db" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
// After: Modular configuration
module "web_server" {
source = "./modules/web"
}
module "db_server" {
source = "./modules/db"
}
| Refactoring Step | Description | Action |
|---|---|---|
| Identify Common Patterns | Find repeated resource blocks or configurations | Review existing Terraform code for duplication |
| Create Module Structure | Organize resources into module directories | Create modules/web/main.tf, etc. |
| Extract Configuration | Migrate shared logic to module files | Move resources and variables into modules |
| Update References | Replace old resource references with module calls | Use module.<name> syntax |
Note: Ensure all dependencies between modules are properly declared to avoid runtime errors.
Workflow: Refactoring Process
- Identify resources that can be grouped into modules
- Create a new directory structure for modules
- Move configuration files into appropriate module directories
- Define input variables and output values for each module
- Update root configuration to use modules instead of direct resources
- Test module functionality with
terraform plan
12. Using Module Providers
| Property | Description | Example |
|---|---|---|
| provider | Defines the provider configuration for a module | provider "aws" { region = "us-west-2" } |
| source | Specifies the module source location | source = "terraform-aws-modules/vpc/aws" |
| version | Version constraint for the module | version = "~> 3.0" |
Example: Using Module Providers
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 3.0"
providers = {
aws = aws
}
vpc_cidr = "10.0.0.0/16"
}
Note: The
providers map in a module allows you to override which provider instances are used by the module.
Warning: Ensure that provider configurations in modules match the version and configuration of the root module providers.