AAWSLearn
Back to Blog
DevOps12 min read

Terraform Modules: Best Practices for Production

Structure reusable Terraform modules with proper state management, versioning, and CI/CD integration.

#Terraform#IaC#DevOps

As your cloud footprint grows, writing plain, copy-pasted Terraform files quickly becomes a maintenance nightmare. Terraform modules are the key to building dry (Don't Repeat Yourself), scalable, and reusable infrastructure configurations.

In this guide, we'll explore production best practices for structuring, versioning, and consuming Terraform modules.


A standard, reusable module should have its own git repository and follow this structure:

terraform-aws-vpc/
├── LICENSE
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── examples/
│   └── simple-vpc/
│       ├── main.tf
│       ├── outputs.tf
│       └── variables.tf
└── test/
    └── vpc_test.go
  • main.tf: Contains the actual resource declarations (e.g., aws_vpc, aws_subnet).
  • variables.tf: Input variables with descriptive descriptions and validation rules.
  • outputs.tf: Key outputs needed by parent modules (e.g., VPC ID, subnet IDs).
  • examples/: Working examples showing users how to call the module.

Writing a Clean Module Interface

Always validate your input variables using custom validations. This guarantees that misconfigurations are caught locally during terraform plan instead of failing mid-apply.

Here is an example in HCL for a subnet configuration block with strict tag validation:

# variables.tf
variable "vpc_cidr" {
  type        = string
  description = "The CIDR block for the VPC"
  default     = "10.0.0.0/16"

  validation {
    condition     = can(regex("^10\\.\\d+\\.\\d+\\.\\d+/\\d+$", var.vpc_cidr))
    error_message = "The VPC CIDR block must be a valid 10.x.x.x block."
  }
}

variable "environment" {
  type        = string
  description = "Application deployment environment"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "The environment variable must be either dev, staging, or prod."
  }
}

Module Versioning Best Practices

Never reference a module using a local directory relative path (source = "../modules/vpc") for production deployments. Instead, use a versioned git source reference:

# main.tf in target environment
module "production_vpc" {
  source  = "github.com/awslearn/terraform-aws-vpc?ref=v1.4.2"
  
  vpc_cidr    = "10.100.0.0/16"
  environment = "prod"
}

By lock-stepping module versions with git tags, you can make updates to modules in isolated branches without breaking production environments.

Related Articles

DevOps

CI/CD with GitHub Actions and AWS

Build a complete deployment pipeline using GitHub Actions, OIDC federation, and AWS CodeDeploy or ECS.

Search AWSLearn

Search for AWS VPC, Karpenter, Docker builds, Linux runlevels, or Terraform states.