Terragrunt for Beginners

Terragrunt Modules

Creating Your Own Module From Scratch

Building a well-structured, versioned Terraform module ensures consistency, reusability, and easy collaboration across teams. In this guide, we’ll walk through:

  • Directory layout
  • Input declarations and validations
  • Resource definitions
  • Output exports
  • Example usage
  • Versioning best practices
  • Documentation tips

1. Module Directory Structure

Begin by creating a top-level folder for your module. Inside, organize files as follows:

PathPurpose
my-module/Root directory for your module
├─ main.tfCore resource definitions
├─ variables.tfInput variable declarations & validations
├─ outputs.tfExported values for external configurations
└─ examples/simple/main.tfMinimal example demonstrating module usage
my-module/
├── main.tf
├── variables.tf
├── outputs.tf
└── examples/
    └── simple/
        └── main.tf

2. Declare and Parameterize Inputs

In variables.tf, declare all module inputs, set types, defaults, and add validation:

variable "instance_count" {
  description = "Number of EC2 instances to create"
  type        = number
  default     = 1

  validation {
    condition     = var.instance_count > 0
    error_message = "instance_count must be at least 1"
  }
}

variable "ami_id" {
  description = "AMI ID for the EC2 instances"
  type        = string
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

variable "aws_region" {
  description = "AWS region to deploy resources"
  type        = string
  default     = "us-west-2"
}

Note

Use validation blocks to enforce constraints early and prevent invalid configurations.


3. Define Resources Using Inputs

Start by pinning provider versions:

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

provider "aws" {
  region = var.aws_region
}

Then, reference your variables in main.tf:

resource "aws_instance" "example" {
  count         = var.instance_count
  ami           = var.ami_id
  instance_type = var.instance_type

  tags = {
    Name = "example-instance-${count.index + 1}"
  }
}

Warning

Avoid hard-coding AMI IDs across regions. Consider using data "aws_ami" to dynamically look up the latest image.


4. Expose Outputs

In outputs.tf, export the values that other modules or root configurations will consume:

output "instance_ids" {
  description = "IDs of the created EC2 instances"
  value       = aws_instance.example[*].id
}

output "public_ips" {
  description = "Public IP addresses of the created EC2 instances"
  value       = aws_instance.example[*].public_ip
}

Note

If outputs contain sensitive data (e.g., private keys), set sensitive = true to prevent accidental exposure.


5. Provide Usage Examples

Under examples/simple/main.tf, demonstrate a minimal working invocation:

module "ec2_example" {
  source         = "../../"
  instance_count = 2
  ami_id         = "ami-0c55b159cbfafe1f0"
  instance_type  = "t3.micro"
  aws_region     = "us-west-2"
}

output "created_ids" {
  value = module.ec2_example.instance_ids
}

Run the following commands:

CommandDescription
terraform initInitialize plugins and backend
terraform planPreview planned changes
terraform applyApply the configuration
terraform destroyTear down the created resources
cd examples/simple
terraform init
terraform apply

6. Versioning and Collaboration

Follow these guidelines to keep your module maintainable:

  • Use Semantic Versioning (e.g., v1.0.0, v1.1.0)
  • Tag releases in Git (git tag v1.0.0)
  • Maintain a CHANGELOG.md to record feature additions and fixes

7. Documentation and Comments

Consistently document your module to help users onboard quickly:

  • Add a header comment in each .tf file summarizing its purpose
  • Comment complex logic (e.g., loops, conditionals) in main.tf
  • Describe variable constraints and recommended values next to their declarations

Watch Video

Watch video content

Previous
Custom Modules vs Community Modules