This guide demonstrates how to structure a Terraform project using modules. Modularization is a best practice for managing complex infrastructure, promoting reusability, maintainability, and consistency across different environments. In this example, we'll create modules for VPC, EC2, RDS, and S3.
A modular Terraform project typically has a root module that orchestrates calls to sub-modules. The modules/
directory contains the definitions for each reusable component.
aws-terraform-modular/
│
├── main.tf # Root module: Defines the overall infrastructure by calling sub-modules
├── variables.tf # Root module: Declares global variables
├── outputs.tf # Root module: Defines outputs from the overall deployment
├── provider.tf # Root module: Configures the AWS provider
├── terraform.tfvars # (Optional) Stores variable values for the root module (e.g., sensitive data)
│
└── modules/ # Directory containing all reusable modules
├── vpc/ # VPC module: Manages VPC, subnets, and security groups
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── ec2/ # EC2 module: Manages EC2 instances
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── rds/ # RDS module: Manages RDS database instances
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── s3/ # S3 module: Manages S3 buckets
├── main.tf
├── variables.tf
└── outputs.tf
The files in the aws-terraform-modular/
directory form the "root module." They define the overall deployment by referencing and configuring the sub-modules.
provider.tf
This file configures the AWS provider for the entire project.
provider "aws" {
region = var.aws_region # Uses the 'aws_region' variable defined in the root variables.tf
}
variables.tf
This file declares variables that are used at the root level or passed down to modules.
variable "aws_region" {
description = "The AWS region where resources will be deployed."
type = string
default = "ap-southeast-2" # Default region set to Sydney
}
variable "db_password" {
description = "The master password for the RDS admin user."
type = string
sensitive = true # Marks the variable as sensitive
}
main.tf
This is where you define the instances of your modules and connect their outputs to inputs of other modules.
# Call the VPC module
module "vpc" {
source = "./modules/vpc" # Path to the VPC module directory
vpc_cidr = "10.0.0.0/16"
subnet_cidr = "10.0.1.0/24"
aws_region = var.aws_region
}
# Call the EC2 module
module "ec2" {
source = "./modules/ec2" # Path to the EC2 module directory
subnet_id = module.vpc.subnet_id # Get subnet ID from VPC module output
sg_id = module.vpc.sg_id # Get security group ID from VPC module output
instance_type = "t3.micro"
aws_region = var.aws_region
}
# Call the RDS module
module "rds" {
source = "./modules/rds" # Path to the RDS module directory
subnet_ids = [module.vpc.subnet_id] # Pass subnet ID as a list
sg_id = module.vpc.sg_id # Get security group ID from VPC module output
db_username = "admin"
db_password = var.db_password # Get sensitive password from root variable
aws_region = var.aws_region
}
# Call the S3 module
module "s3" {
source = "./modules/s3" # Path to the S3 module directory
# S3 bucket names must be globally unique. Choose a unique name.
bucket_name = "my-unique-terraform-demo-bucket-12345-abcde"
}
outputs.tf
This file defines the outputs that will be displayed after the terraform apply
command, pulling values from the outputs of the called modules.
output "ec2_public_ip" {
description = "The public IP address of the demo EC2 instance."
value = module.ec2.public_ip # Get output from the EC2 module
}
output "rds_endpoint" {
description = "The connection endpoint for the demo RDS database."
value = module.rds.endpoint # Get output from the RDS module
}
output "s3_bucket_name" {
description = "The name of the created S3 bucket."
value = module.s3.bucket_name # Get output from the S3 module
}