Skip to main content

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
module "my_module" {
  source = "./modules/my-module"
}
Git Repository github.com/username/repo//module-path Module stored in a Git repository (e.g., GitHub, GitLab)
module "my_module" {
  source = "github.com/username/repo//module-path"
}
Registry registry.terraform.io/username/module-name Module from the Terraform Registry (public or private)
module "my_module" {
  source = "registry.terraform.io/username/module-name"
}
HTTP/HTTPS https://example.com/module.zip Module provided as a ZIP archive via HTTP/HTTPS
module "my_module" {
  source = "https://example.com/module.zip"
}
S3 Bucket s3::https://s3.amazonaws.com/bucket-name/module.zip Module stored in an S3-compatible storage bucket
module "my_module" {
  source = "s3::https://s3.amazonaws.com/bucket-name/module.zip"
}

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 master or main branches in production configurations
  • Use terraform init to 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
output "instance_id" {
  value = aws_instance.my_instance.id
}
vpc_cidr_block string CIDR block of the VPC
output "vpc_cidr" {
  value = aws_vpc.main.cidr_block
}
public_ips list(string) List of public IP addresses of instances
output "instance_ips" {
  value = aws_instance.web.public_ip
}

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
module "aws_instance" {
  source = "./modules/aws-instance"
}
Source can be local path, Git repo, or registry
version Specifies the version of a module
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "3.14.0"
}
Ensures consistent behavior across environments
providers Maps module providers to local provider aliases
module "ec2" {
  source = "./modules/ec2"
  providers = {
    aws = aws.us_west_2
  }
}
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

  1. Identify resources that can be grouped into modules
  2. Create a new directory structure for modules
  3. Move configuration files into appropriate module directories
  4. Define input variables and output values for each module
  5. Update root configuration to use modules instead of direct resources
  6. 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.

Was this article helpful?