Skip to main content

Command Palette

Search for a command to run...

Terraform Mastery – Day 16: Best Practices for Module 1

Published
4 min read

Introduction

Terraform modules help you organize and reuse your infrastructure code efficiently. However, to ensure scalability, maintainability, and efficiency, it's crucial to follow best practices when designing and using modules. In this guide, we'll explore key best practices to create well-structured and reusable Terraform modules.


1. Keep Modules Small & Purpose-Driven

A module should serve a specific purpose, such as setting up a VPC, configuring an EC2 instance, or managing IAM roles. Avoid bloated modules that try to handle everything. Instead, break them into smaller, independent modules.

Example:

  • Good: A module that only manages S3 bucket creation.

  • Bad: A single module that creates S3 buckets, IAM roles, and EC2 instances together.


2. Follow a Consistent Directory Structure

A well-structured module makes it easy to navigate and maintain. Use this structure:

module-name/
│── main.tf        # Core resource definitions
│── variables.tf   # Input variables
│── outputs.tf     # Output values
│── README.md      # Documentation
│── versions.tf    # Required provider versions

This format keeps modules organized and easy to reuse.


3. Use Input Variables for Flexibility

Instead of hardcoding values, use input variables (variables.tf) to make modules reusable.

Example:

variable "bucket_name" {
  description = "The name of the S3 bucket"
  type        = string
}

resource "aws_s3_bucket" "example" {
  bucket = var.bucket_name
}

This allows users to specify different bucket names without modifying the module.


4. Define Outputs for Useful Data

Use output values (outputs.tf) to expose necessary information from a module, such as an instance ID, VPC ID, or bucket name.

Example:

output "bucket_arn" {
  description = "The ARN of the S3 bucket"
  value       = aws_s3_bucket.example.arn
}

This allows other modules to reference the output values.


5. Pin Provider & Module Versions

To ensure stability and avoid breaking changes, always specify provider and module versions.

Example (versions.tf):

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  required_version = ">= 1.3.0"
}

This prevents Terraform from upgrading to an incompatible version automatically.


6. Write Clear Documentation

Documenting your module ensures ease of use for yourself and your team. Your README.md should include:
✔️ Module Purpose
✔️ Input Variables & Outputs
✔️ Example Usage

Example (README.md):

# S3 Bucket Module

## Usage
```hcl
module "s3_bucket" {
  source      = "./modules/s3"
  bucket_name = "my-unique-bucket"
}

Inputs

  • bucket_name (string) – Name of the S3 bucket

Outputs

  • bucket_arn – ARN of the created S3 bucket
Good documentation **saves time** and prevents confusion.  

---  

## **7. Use Remote State for Module Composition**  
When using modules that depend on each other, **use remote state** to pass data between them instead of hardcoding values.  

✅ **Example (Referencing VPC ID from Another Module):**  
```hcl
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "terraform-state-bucket"
    key    = "network/terraform.tfstate"
    region = "us-east-1"
  }
}

module "ec2" {
  source   = "./modules/ec2"
  vpc_id   = data.terraform_remote_state.network.outputs.vpc_id
}

This ensures modules remain loosely coupled and scalable.


8. Keep Modules DRY (Don’t Repeat Yourself)

If you find yourself copy-pasting the same module multiple times, use count or for_each to make it more dynamic.

Example (Creating Multiple S3 Buckets with for_each):

variable "bucket_names" {
  type = list(string)
  default = ["bucket-1", "bucket-2"]
}

resource "aws_s3_bucket" "example" {
  for_each = toset(var.bucket_names)
  bucket   = each.value
}

This reduces redundancy and improves maintainability.


9. Use Module Registries for Reusability

Instead of writing modules from scratch every time, consider using or publishing modules on:
📌 Terraform Registry (registry.terraform.io)
📌 Private Module Repositories

Example (Using a Prebuilt AWS VPC Module):

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "3.19.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"
}

This saves time and ensures best practices are followed.


Conclusion

Following these best practices ensures your Terraform modules are scalable, reusable, and maintainable. By keeping modules small, using variables, outputs, documentation, and enforcing version control, you create a robust Infrastructure as Code (IaC) setup.

👉 In the next article, we'll cover Best Practices for Module 2, diving deeper into module composition and advanced structuring. Stay tuned! 🚀