Modules
Modules are containers for multiple resources that are used together. They enable you to organize, encapsulate, and reuse Terraform configurations.
What is a Module?
A module is a collection of .tf files in a directory. Every Terraform configuration has at least one module, called the root module , which consists of the resources defined in the .tf files in the main working directory.
Module Structure
module/
├── main.tf # Primary resources
├── variables.tf # Input variable declarations
├── outputs.tf # Output value declarations
└── README.md # Documentation
Calling Modules
Use module blocks to call child modules:
module "<NAME>" {
source = "<SOURCE>"
# Input variables
< VAR_NAME > = < VALUE >
}
Basic Example
module "web_app" {
source = "./modules/web-app"
instance_type = "t2.micro"
instance_name = "WebServer"
}
Module Sources
The source argument specifies where the module code is located.
Local Paths
module "network" {
source = "./modules/network"
}
module "compute" {
source = "../shared-modules/compute"
}
Local paths must begin with ./ or ../ to distinguish them from module registry addresses.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
}
Git Repositories
module "vpc" {
source = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git"
}
# Specific branch
module "vpc" {
source = "git::https://github.com/example/repo.git?ref=v1.2.0"
}
# Specific subdirectory
module "vpc" {
source = "git::https://github.com/example/repo.git//modules/vpc"
}
HTTP URLs
module "vpc" {
source = "https://example.com/vpc-module.zip"
}
S3 Buckets
module "vpc" {
source = "s3::https://s3-eu-west-1.amazonaws.com/examplebucket/vpc.zip"
}
Module Arguments
Location of the module source code. source = "./modules/web-app"
Version constraint for registry modules.
Create multiple instances of the module.
Create instances from a map or set. for_each = var . environments
Explicit dependencies. depends_on = [ aws_vpc . main ]
Pass provider configurations to the module. providers = {
aws = aws.west
}
Child Module Variables
# modules/web-app/variables.tf
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
variable "instance_name" {
description = "Name tag for the instance"
type = string
}
Passing Values
module "web_app" {
source = "./modules/web-app"
instance_type = "t2.large"
instance_name = "Production-Web"
}
Module Outputs
Declaring Outputs
# modules/web-app/outputs.tf
output "instance_id" {
value = aws_instance . app . id
description = "ID of the EC2 instance"
}
output "public_ip" {
value = aws_instance . app . public_ip
}
Using Module Outputs
module "web_app" {
source = "./modules/web-app"
}
resource "aws_route53_record" "web" {
zone_id = aws_route53_zone . main . zone_id
name = "web.example.com"
type = "A"
ttl = 300
records = [ module . web_app . public_ip ]
}
output "web_instance_id" {
value = module . web_app . instance_id
}
Module Count and For Each
Using Count
module "web_app" {
source = "./modules/web-app"
count = 3
instance_name = "web- ${ count . index } "
}
# Reference specific instance
output "first_instance" {
value = module . web_app [ 0 ] . instance_id
}
# Reference all instances
output "all_instances" {
value = module . web_app [ * ] . instance_id
}
Using For Each
module "web_app" {
source = "./modules/web-app"
for_each = var . environments
instance_type = each . value . instance_type
instance_name = " ${ each . key } -web"
}
# Reference specific instance
output "prod_instance" {
value = module . web_app [ "production" ] . instance_id
}
Passing Providers
Explicitly pass provider configurations to modules:
provider "aws" {
region = "us-west-2"
}
provider "aws" {
alias = "east"
region = "us-east-1"
}
module "web_app_west" {
source = "./modules/web-app"
providers = {
aws = aws
}
}
module "web_app_east" {
source = "./modules/web-app"
providers = {
aws = aws.east
}
}
Module Best Practices
1. Use Variables for Flexibility
# modules/web-app/variables.tf
variable "environment" {
type = string
}
variable "instance_type" {
type = string
}
variable "tags" {
type = map ( string )
default = {}
}
2. Document with Descriptions
variable "vpc_cidr" {
description = "CIDR block for the VPC. Must be a valid IPv4 CIDR."
type = string
validation {
condition = can ( cidrhost (var . vpc_cidr , 0 ))
error_message = "Must be a valid IPv4 CIDR block."
}
}
3. Expose Useful Outputs
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc . main . id
}
output "subnet_ids" {
description = "List of subnet IDs"
value = aws_subnet . private [ * ] . id
}
4. Use Semantic Versioning
For published modules:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0" # Accept patch and minor updates
}
Module Composition
Build complex infrastructure by composing modules:
module "network" {
source = "./modules/network"
vpc_cidr = "10.0.0.0/16"
}
module "database" {
source = "./modules/database"
vpc_id = module . network . vpc_id
subnet_ids = module . network . private_subnet_ids
security_group = module . network . database_security_group_id
}
module "application" {
source = "./modules/application"
vpc_id = module . network . vpc_id
subnet_ids = module . network . public_subnet_ids
database_endpoint = module . database . endpoint
}
Example Module
Module Definition
# modules/web-app/main.tf
resource "aws_instance" "app" {
ami = var . ami_id
instance_type = var . instance_type
subnet_id = var . subnet_id
tags = merge (
var . tags ,
{
Name = var.instance_name
}
)
}
resource "aws_security_group" "app" {
name_prefix = " ${ var . instance_name } -"
vpc_id = var . vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = [ "0.0.0.0/0" ]
}
}
# modules/web-app/variables.tf
variable "ami_id" {
description = "AMI ID for the instance"
type = string
}
variable "instance_type" {
description = "Instance type"
type = string
default = "t2.micro"
}
variable "instance_name" {
description = "Name for the instance"
type = string
}
variable "vpc_id" {
description = "VPC ID"
type = string
}
variable "subnet_id" {
description = "Subnet ID"
type = string
}
variable "tags" {
description = "Additional tags"
type = map ( string )
default = {}
}
# modules/web-app/outputs.tf
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance . app . id
}
output "public_ip" {
description = "Public IP of the instance"
value = aws_instance . app . public_ip
}
output "security_group_id" {
description = "ID of the security group"
value = aws_security_group . app . id
}
Module Usage
module "web_app" {
source = "./modules/web-app"
ami_id = "ami-12345678"
instance_type = "t2.large"
instance_name = "production-web"
vpc_id = aws_vpc . main . id
subnet_id = aws_subnet . public . id
tags = {
Environment = "production"
Team = "platform"
}
}
Module Example from Source
From internal/configs/testdata/config-build/root.tf:
module "child_a" {
source = "./child_a"
}
module "child_b" {
source = "./child_b"
}
Module Registry
Public modules are available on the Terraform Registry :
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = [ "us-west-2a" , "us-west-2b" ]
private_subnets = [ "10.0.1.0/24" , "10.0.2.0/24" ]
public_subnets = [ "10.0.101.0/24" , "10.0.102.0/24" ]
}
Private Module Registry
Host private modules in a registry:
module "vpc" {
source = "app.terraform.io/example-corp/vpc/aws"
version = "1.0.0"
}
Implementation Details
From internal/configs/module_call.go:18-41:
type ModuleCall struct {
Name string
SourceAddr addrs . ModuleSource
SourceAddrRaw string
SourceAddrRange hcl . Range
SourceSet bool
Config hcl . Body
Version VersionConstraint
Count hcl . Expression
ForEach hcl . Expression
Providers [] PassedProviderConfig
DependsOn [] hcl . Traversal
DeclRange hcl . Range
IgnoreNestedDeprecations bool
}
Best Practices
Single Responsibility Each module should have a single, well-defined purpose.
Minimal Inputs Expose only necessary inputs. Provide sensible defaults.
Useful Outputs Output values that other modules or configurations might need.
Version Constraints Always specify version constraints for external modules.
Next Steps
Variables Define module inputs
Outputs Export module values