Terraform Module
Overview¶
This template provides a complete structure for creating reusable, well-documented Terraform modules following industry best practices. Use this as a starting point for building modules that are maintainable, testable, and easy to consume.
Module Structure¶
terraform-<provider>-<name>/
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── examples/
│ ├── simple/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ └── complete/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
├── test/
│ └── module_test.go
└── .gitignore
README.md Template¶
## Terraform [Provider] [Resource Name] Module
Terraform module for creating and managing [resource description].
## Usage
### Simple Example
\```hcl
module "example" {
source = "github.com/your-org/terraform-aws-example"
name = "my-resource"
environment = "production"
tags = {
Project = "my-project"
}
}
\```
### Complete Example
\```hcl
module "example" {
source = "github.com/your-org/terraform-aws-example"
name = "my-resource"
environment = "production"
# Advanced configuration
enable_monitoring = true
retention_days = 30
tags = {
Project = "my-project"
ManagedBy = "Terraform"
}
}
\```
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 1.0 |
| aws | >= 5.0 |
## Providers
| Name | Version |
|------|---------|
| aws | >= 5.0 |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [aws_example_resource.this][aws_example_resource] | resource |
[aws_example_resource]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/example_resource
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| name | Name of the resource | `string` | n/a | yes |
| environment | Environment name | `string` | n/a | yes |
| tags | Tags to apply to resources | `map(string)` | `{}` | no |
## Outputs
| Name | Description |
|------|-------------|
| id | The ID of the resource |
| arn | The ARN of the resource |
## Examples
See the [examples](./examples) directory for working examples.
## Testing
This module uses [Terratest](https://terratest.gruntwork.io/) for automated testing.
\```bash
cd test
go test -v -timeout 30m
\```
## Contributing
Contributions are welcome! Please open an issue or submit a pull request.
## License
Apache 2.0 Licensed. See LICENSE for full details.
main.tf Template¶
## Main resource definitions
resource "aws_example_resource" "this" {
name = var.name
# Configuration
enabled = var.enabled
size = var.size
# Metadata
tags = merge(
var.tags,
{
Name = var.name
Environment = var.environment
ManagedBy = "Terraform"
}
)
}
## Supporting resources
resource "aws_example_policy" "this" {
count = var.enable_policy ? 1 : 0
name = "${var.name}-policy"
description = "Policy for ${var.name}"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:*:*:*"
}
]
})
tags = var.tags
}
variables.tf Template¶
## Required variables
variable "name" {
description = "Name of the resource. Used for resource naming and tagging."
type = string
validation {
condition = length(var.name) > 0 && length(var.name) <= 64
error_message = "Name must be between 1 and 64 characters."
}
}
variable "environment" {
description = "Environment name (e.g., dev, staging, production)."
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}
## Optional variables with defaults
variable "enabled" {
description = "Whether the resource is enabled."
type = bool
default = true
}
variable "size" {
description = "Size of the resource."
type = string
default = "small"
validation {
condition = contains(["small", "medium", "large"], var.size)
error_message = "Size must be small, medium, or large."
}
}
variable "enable_policy" {
description = "Whether to create an IAM policy."
type = bool
default = false
}
variable "retention_days" {
description = "Number of days to retain logs."
type = number
default = 7
validation {
condition = var.retention_days > 0
error_message = "Retention days must be positive."
}
}
## Complex variables
variable "network_config" {
description = "Network configuration for the resource."
type = object({
vpc_id = string
subnet_ids = list(string)
security_group_ids = list(string)
})
default = null
}
## Tags
variable "tags" {
description = "A map of tags to add to all resources."
type = map(string)
default = {}
}
outputs.tf Template¶
## Primary outputs
output "id" {
description = "The ID of the resource."
value = aws_example_resource.this.id
}
output "arn" {
description = "The ARN of the resource."
value = aws_example_resource.this.arn
}
output "name" {
description = "The name of the resource."
value = aws_example_resource.this.name
}
## Conditional outputs
output "policy_arn" {
description = "The ARN of the IAM policy (if enabled)."
value = var.enable_policy ? aws_example_policy.this[0].arn : null
}
## Complex outputs
output "endpoint" {
description = "Endpoint information for the resource."
value = {
url = aws_example_resource.this.endpoint
port = aws_example_resource.this.port
}
}
## Sensitive outputs
output "credentials" {
description = "Credentials for accessing the resource."
value = {
username = aws_example_resource.this.username
password = aws_example_resource.this.password
}
sensitive = true
}
versions.tf Template¶
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
examples/simple/main.tf Template¶
provider "aws" {
region = "us-east-1"
}
module "example" {
source = "../../"
name = "simple-example"
environment = "dev"
tags = {
Example = "simple"
Purpose = "testing"
}
}
output "resource_id" {
description = "The ID of the created resource."
value = module.example.id
}
examples/simple/variables.tf Template¶
variable "aws_region" {
description = "AWS region to deploy resources."
type = string
default = "us-east-1"
}
examples/complete/main.tf Template¶
provider "aws" {
region = var.aws_region
}
## VPC for the example
resource "aws_vpc" "example" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "example-vpc"
Example = "complete"
}
}
resource "aws_subnet" "example" {
vpc_id = aws_vpc.example.id
cidr_block = "10.0.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
tags = {
Name = "example-subnet"
Example = "complete"
}
}
data "aws_availability_zones" "available" {
state = "available"
}
## Complete module usage
module "example" {
source = "../../"
name = "complete-example"
environment = "production"
# Enable all features
enabled = true
size = "large"
enable_policy = true
# Network configuration
network_config = {
vpc_id = aws_vpc.example.id
subnet_ids = [aws_subnet.example.id]
security_group_ids = []
}
# Advanced settings
retention_days = 30
tags = {
Example = "complete"
Purpose = "testing"
ManagedBy = "Terraform"
}
}
## Outputs
output "resource_id" {
description = "The ID of the created resource."
value = module.example.id
}
output "resource_arn" {
description = "The ARN of the created resource."
value = module.example.arn
}
output "endpoint" {
description = "The endpoint of the resource."
value = module.example.endpoint
}
examples/complete/variables.tf Template¶
variable "aws_region" {
description = "AWS region to deploy resources."
type = string
default = "us-east-1"
}
test/module_test.go Template¶
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestTerraformModule(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
// Path to the Terraform code
TerraformDir: "../examples/simple",
// Variables to pass to the Terraform code
Vars: map[string]interface{}{
"aws_region": "us-east-1",
},
// Environment variables
EnvVars: map[string]string{
"AWS_DEFAULT_REGION": "us-east-1",
},
})
// Clean up resources at the end of the test
defer terraform.Destroy(t, terraformOptions)
// Deploy the infrastructure
terraform.InitAndApply(t, terraformOptions)
// Validate outputs
resourceID := terraform.Output(t, terraformOptions, "resource_id")
assert.NotEmpty(t, resourceID)
}
func TestTerraformModuleComplete(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../examples/complete",
Vars: map[string]interface{}{
"aws_region": "us-east-1",
},
EnvVars: map[string]string{
"AWS_DEFAULT_REGION": "us-east-1",
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// Test outputs
resourceID := terraform.Output(t, terraformOptions, "resource_id")
resourceARN := terraform.Output(t, terraformOptions, "resource_arn")
assert.NotEmpty(t, resourceID)
assert.NotEmpty(t, resourceARN)
assert.Contains(t, resourceARN, "arn:aws:")
}
.gitignore Template¶
## Local .terraform directories
**/.terraform/*
## .tfstate files
*.tfstate
*.tfstate.*
## Crash log files
crash.log
crash.*.log
## Exclude all .tfvars files
*.tfvars
*.tfvars.json
## Ignore override files
override.tf
override.tf.json
*_override.tf
*_override.tf.json
## Ignore CLI configuration files
.terraformrc
terraform.rc
## Ignore lock files (commit for modules)
## .terraform.lock.hcl
## Test artifacts
test/.test-data
test/terraform.tfstate*
test/.terraform/*
## IDE
.idea
.vscode
*.swp
*.swo
*.bak
*~
## OS
.DS_Store
Thumbs.db
Best Practices¶
Module Naming¶
terraform-<PROVIDER>-<NAME>
Examples:
- terraform-aws-vpc
- terraform-aws-ec2-instance
- terraform-azure-storage-account
- terraform-google-gke-cluster
Variable Ordering¶
- Required variables (no defaults)
- Optional variables (with defaults)
- Complex variables (objects, maps)
- Tags (always last)
Output Naming¶
Use descriptive, consistent output names:
id- Resource identifierarn- Amazon Resource Namename- Resource nameendpoint- Connection endpointurl- Full URL
Documentation¶
- Always include a comprehensive README
- Use
terraform-docsto auto-generate documentation - Provide working examples for common use cases
- Include validation rules in variable descriptions
Testing¶
- Test with Terratest or similar framework
- Include simple and complete examples
- Test in a separate AWS account
- Clean up resources after testing
References¶
Official Documentation¶
Tools¶
- terraform-docs - Generate documentation
- Terratest - Automated testing
- tflint - Linting
- checkov - Security scanning
Status: Active