# Tsh Implementing Terraform Modules

> Build reusable Terraform modules for AWS, Azure, and GCP infrastructure following infrastructure-as-code best practices. Use when creating infrastructure modules, standardizing cloud provisioning, or implementing reusable IaC components.

- Skill: `thesoftwarehouse/tsh-implementing-terraform-modules` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add thesoftwarehouse/tsh-implementing-terraform-modules`
- Raw SKILL.md: https://api.skillmd.com/api/skills/thesoftwarehouse/tsh-implementing-terraform-modules/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: TheSoftwareHouse (https://skillmd.com/u/thesoftwarehouse)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/thesoftwarehouse/tsh-implementing-terraform-modules

---


# Terraform Module Library

Production-ready Terraform module patterns for AWS, Azure, and GCP infrastructure.

## Purpose

Create reusable, well-tested Terraform modules for common cloud infrastructure patterns across multiple cloud providers.

## When to Use

- Build reusable infrastructure components
- Standardize cloud resource provisioning
- Implement infrastructure as code best practices
- Create multi-cloud compatible modules
- Establish organizational Terraform standards

## Module Structure

```
terraform-modules/
├── aws/
│   ├── vpc/
│   ├── eks/
│   ├── rds/
│   └── s3/
├── azure/
│   ├── vnet/
│   ├── aks/
│   └── storage/
└── gcp/
    ├── vpc/
    ├── gke/
    └── cloud-sql/
```

## Standard Module Pattern

```
module-name/
├── main.tf          # Main resources
├── variables.tf     # Input variables
├── outputs.tf       # Output values
├── versions.tf      # Provider versions
├── README.md        # Documentation
├── examples/        # Usage examples
│   └── complete/
│       ├── main.tf
│       └── variables.tf
└── tests/           # Terratest files
    └── module_test.go
```

## AWS VPC Module Example

**main.tf:**

```hcl
resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = var.enable_dns_hostnames
  enable_dns_support   = var.enable_dns_support

  tags = merge(
    {
      Name = var.name
    },
    var.tags
  )
}

resource "aws_subnet" "private" {
  count             = length(var.private_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = merge(
    {
      Name = "${var.name}-private-${count.index + 1}"
      Tier = "private"
    },
    var.tags
  )
}

resource "aws_internet_gateway" "main" {
  count  = var.create_internet_gateway ? 1 : 0
  vpc_id = aws_vpc.main.id

  tags = merge(
    {
      Name = "${var.name}-igw"
    },
    var.tags
  )
}
```

**variables.tf:**

```hcl
variable "name" {
  description = "Name of the VPC"
  type        = string
}

variable "cidr_block" {
  description = "CIDR block for VPC"
  type        = string
  validation {
    condition     = can(cidrnetmask(var.cidr_block))
    error_message = "CIDR block must be valid IPv4 CIDR notation."
  }
}

variable "availability_zones" {
  description = "List of availability zones"
  type        = list(string)
}

variable "private_subnet_cidrs" {
  description = "CIDR blocks for private subnets"
  type        = list(string)
  default     = []
}

variable "enable_dns_hostnames" {
  description = "Enable DNS hostnames in VPC"
  type        = bool
  default     = true
}

variable "enable_dns_support" {
  description = "Enable DNS support in VPC"
  type        = bool
  default     = true
}

variable "create_internet_gateway" {
  description = "Whether to create an Internet Gateway"
  type        = bool
  default     = true
}

variable "tags" {
  description = "Additional tags"
  type        = map(string)
  default     = {}
}
```

**outputs.tf:**

```hcl
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "private_subnet_ids" {
  description = "IDs of private subnets"
  value       = aws_subnet.private[*].id
}

output "vpc_cidr_block" {
  description = "CIDR block of VPC"
  value       = aws_vpc.main.cidr_block
}
```

## Best Practices

1. **Use semantic versioning** for modules
2. **Pin provider versions** in versions.tf — use `aws ~> 6.0`, `azurerm ~> 4.0`, `google ~> 5.0`
3. **Document all variables** with descriptions
4. **Provide examples** in examples/ directory
5. **Use validation blocks** for input validation
6. **Output important attributes** for module composition
7. **Use locals** for computed values
8. **Implement conditional resources** with count/for_each
9. **Test modules** with Terratest
10. **Tag all resources** consistently

## Reference Files

- `references/aws-modules.md` - AWS module patterns
- `references/azure-modules.md` - Azure module patterns
- `references/gcp-modules.md` - GCP module patterns

## Testing

Use **Terratest** (Go) to test Terraform modules. Every module must include:

- `examples/complete/` — a root module that calls the module under test with realistic values and re-exports its outputs
- `tests/module_test.go` — Go test file that runs `InitAndApply`, reads outputs, asserts expected values, and always defers `Destroy`

Apply the following rules when writing tests:
- Always call `t.Parallel()`
- Always wrap options with `terraform.WithDefaultRetryableErrors`
- Always use `runtime.Caller(0)` to resolve `examples/complete/` path relative to the test file — never use hardcoded relative paths
- Always use `single_nat_gateway = true` (or equivalent cost-reducing flags) in test examples
- Provide a **plan-only** variant of each test (using `InitAndPlanAndShowWithStruct`) for fast PR validation that requires no AWS credentials
- Use a dedicated AWS test account — never run against production
- Set `-timeout 30m` in CI to avoid hanging runs

## Terraform vs Terragrunt Decision

Use **plain Terraform** when:
- Single environment, single region
- 2–3 environments in the same region (use workspaces or directory layout)
- Existing project without Terragrunt (don't migrate mid-project)

Use **Terragrunt** when:
- 4+ environments or multi-region deployments
- Monorepo with many independent stacks (need `run-all`, dependency orchestration)
- Team needs strict environment parity via inheritance
- Multi-account AWS (landing zone pattern)
- Greenfield with expected growth

**Terragrunt Golden Path structure:**
```
infrastructure/
├── terragrunt.hcl              # Root config (remote_state, generate provider)
├── _envcommon/                  # Shared module references
│   ├── vpc.hcl
│   ├── eks.hcl
│   └── rds.hcl
├── dev/
│   ├── env.hcl                 # Environment-level vars
│   ├── vpc/terragrunt.hcl
│   └── eks/terragrunt.hcl
├── staging/
│   └── ...
└── prod/
    └── ...
```

## Related Skills

- `tsh-designing-multi-cloud-architecture` - For architectural decisions
- `tsh-optimizing-cloud-cost` - For cost-effective designs

