AWS Terraform Modular Project Example

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.

1. Directory Structure

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

2. Root Module Files

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
}

3. Module Definitions