Claude
Skills
Sign in
Back

terraform-module-design

Included with Lifetime
$97 forever

Designs and builds reusable Terraform modules. Use when creating reusable infrastructure patterns, encapsulating complex resource groups, standardizing configurations across projects, or organizing code for maintainability. Covers module structure, versioning, composition, and best practices for production modules.

Design

What this skill does


# Terraform Module Design Skill

## Table of Contents

**Quick Start** → [What Is This](#purpose) | [When to Use](#when-to-use) | [Simple Example](#quick-start)

**How to Implement** → [Step-by-Step](#instructions) | [Examples](#examples)

**Help** → [Requirements](#requirements) | [See Also](#see-also)

## Purpose

Master module design for creating reusable, testable, well-documented infrastructure components. Learn when to modularize, module structure patterns, input/output design, and composition strategies.

## When to Use

Use this skill when you need to:

- **Create reusable infrastructure patterns** - Build modules for repeated resource groups
- **Encapsulate complex configurations** - Simplify multi-resource setups
- **Standardize across projects** - Ensure consistency in infrastructure
- **Organize code for maintainability** - Structure large Terraform projects
- **Build composable systems** - Combine modules to create larger architectures
- **Version infrastructure components** - Publish and version modules

**When NOT to use:**
- Single one-off resources
- Resources used only once
- No variation between uses

**Trigger Phrases:**
- "Create reusable Terraform module"
- "Design module structure"
- "Define module inputs and outputs"
- "Compose multiple modules"
- "Version Terraform module"

## Quick Start

Create a reusable Pub/Sub module in 5 minutes:

```bash
# 1. Create module structure
mkdir -p modules/pubsub-topic
cd modules/pubsub-topic

# 2. Create files
cat > main.tf << 'EOF'
resource "google_pubsub_topic" "topic" {
  name                    = var.topic_name
  message_retention_duration = "${var.retention_days * 86400}s"
  labels                  = var.labels
}

resource "google_pubsub_subscription" "subscription" {
  name  = "${var.topic_name}-sub"
  topic = google_pubsub_topic.topic.name

  dead_letter_policy {
    dead_letter_topic     = google_pubsub_topic.dlq.id
    max_delivery_attempts = var.max_delivery_attempts
  }
}

resource "google_pubsub_topic" "dlq" {
  name = "${var.topic_name}-dlq"
}
EOF

cat > variables.tf << 'EOF'
variable "topic_name" {
  type        = string
  description = "Name of the Pub/Sub topic"
}

variable "retention_days" {
  type        = number
  default     = 7
  description = "Message retention in days"
}

variable "max_delivery_attempts" {
  type        = number
  default     = 5
  description = "Max delivery attempts before DLQ"
}

variable "labels" {
  type        = map(string)
  default     = {}
  description = "Resource labels"
}
EOF

cat > outputs.tf << 'EOF'
output "topic_name" {
  value       = google_pubsub_topic.topic.name
  description = "Name of the Pub/Sub topic"
}

output "subscription_name" {
  value       = google_pubsub_subscription.subscription.name
  description = "Name of the subscription"
}
EOF

# 3. Use module
cd ../..
cat > main.tf << 'EOF'
module "incoming_charges" {
  source = "./modules/pubsub-topic"

  topic_name  = "supplier-charges-incoming"
  retention_days = 7
  labels = {
    environment = "production"
  }
}
EOF

# 4. Deploy
terraform init
terraform apply
```

## Instructions

### Step 1: Decide When to Create a Module

**Create a Module When**:
- ✅ Pattern repeats multiple times in your code
- ✅ Encapsulates complex resource group (5+ related resources)
- ✅ Has configurable inputs that vary per use
- ✅ Produces clear outputs for other modules to consume

**Don't Create a Module For**:
- ❌ Single one-off resources
- ❌ Resources used only once
- ❌ No variation between uses
- ❌ Over-engineering simple setup

**Example**: Pub/Sub topic + subscription + DLQ + IAM (4 resources, repeats twice)
→ Perfect candidate for a module!

### Step 2: Understand Module Structure

**Standard Module Layout**:
```
modules/pubsub-topic/
├── main.tf           # Resources
├── variables.tf      # Input variables
├── outputs.tf        # Output values
├── versions.tf       # Provider requirements
└── README.md         # Documentation
```

**Root Module** (your main configuration):
```
terraform/
├── main.tf          # Provider, variables
├── iam.tf           # IAM resources
├── pubsub.tf        # Pub/Sub resources
├── modules/         # Local modules
│   └── pubsub-topic/
├── .terraform.lock.hcl
└── terraform.tfvars
```

**Module Paths**:
```hcl
# Local module
module "incoming" {
  source = "./modules/pubsub-topic"
}

# Remote module (GitHub)
module "incoming" {
  source = "github.com/org/terraform-modules/pubsub-topic"
  version = "~> 1.0"
}

# Terraform Registry
module "incoming" {
  source = "hashicorp/vault/aws"
  version = "~> 0.2"
}
```

### Step 3: Design Module Inputs (Variables)

**Principles**:
- Keep inputs simple and intuitive
- Use descriptive names
- Provide sensible defaults
- Validate constraints

**Example - Pub/Sub Module**:
```hcl
# variables.tf
variable "topic_name" {
  description = "Name of the Pub/Sub topic"
  type        = string

  validation {
    condition     = length(var.topic_name) > 0 && length(var.topic_name) <= 255
    error_message = "Topic name must be 1-255 characters"
  }
}

variable "retention_days" {
  description = "Days to retain messages (0 = unlimited)"
  type        = number
  default     = 7

  validation {
    condition     = var.retention_days >= 0 && var.retention_days <= 365
    error_message = "Retention must be 0-365 days"
  }
}

variable "labels" {
  description = "Resource labels"
  type        = map(string)
  default     = {}

  validation {
    condition     = alltrue([for k, v in var.labels : length(k) > 0 && length(v) > 0])
    error_message = "Labels must have non-empty keys and values"
  }
}

variable "enable_dlq" {
  description = "Enable Dead Letter Queue"
  type        = bool
  default     = true
}
```

**Input Design Patterns**:
```hcl
# Simple inputs
variable "name" { type = string }

# With defaults
variable "replica_count" { type = number; default = 3 }

# Lists
variable "allowed_ips" { type = list(string); default = [] }

# Maps (configuration objects)
variable "config" {
  type = map(object({
    retention_days = number
    dlq_enabled    = bool
  }))
}

# Flexible object
variable "topic_config" {
  type = object({
    retention_days    = number
    enable_dlq        = bool
    max_retries       = number
  })
  default = {
    retention_days = 7
    enable_dlq     = true
    max_retries    = 5
  }
}
```

### Step 4: Design Module Outputs

**Principles**:
- Export only necessary values
- Use descriptive names
- Document what each output is
- Mark sensitive outputs

**Example - Pub/Sub Module**:
```hcl
# outputs.tf
output "topic_id" {
  description = "Topic resource ID"
  value       = google_pubsub_topic.topic.id
}

output "topic_name" {
  description = "Topic name"
  value       = google_pubsub_topic.topic.name
}

output "subscription_name" {
  description = "Subscription name"
  value       = google_pubsub_subscription.subscription.name
}

output "dlq_topic_name" {
  description = "Dead Letter Queue topic name"
  value       = google_pubsub_topic.dlq.name
}

# Sensitive output
output "configuration" {
  description = "Complete module configuration"
  value = {
    topic_name = google_pubsub_topic.topic.name
    dlq_name   = google_pubsub_topic.dlq.name
  }
  sensitive = true
}
```

**Output Best Practices**:
```hcl
# ✅ GOOD: Specific, documented
output "topic_id" {
  description = "Google resource ID of the topic"
  value       = google_pubsub_topic.topic.id
}

# ❌ BAD: Vague, undocumented
output "id" {
  value = google_pubsub_topic.topic.id
}

# ✅ GOOD: Sensible defaults to avoid null values
output "labels" {
  description = "Applied labels"
  value       = merge(var.labels, { module = "pubsub" })
}
```

### Step 5: Create Module Documentation

**README.md Format**:
```markdown
# PubSub Topic Module

Creates a Pub/Sub topic with optional Dead Letter Queue.

## Usage

```hcl
module "incoming_charges" {
  source = "./modules/pubsub-topic"

  topic_name       = "charges-incoming"
  retention_days   = 7
  enable_dlq       = true
}

Related in Design