# Terraform Skill

> Terraform infrastructure as code best practices Use when this capability is needed.

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

---

@ Terraform Skill for Claude

Comprehensive Terraform and OpenTofu guidance covering testing, modules, CI/CD, and production patterns. Based on terraform-best-practices.com and enterprise experience.

@ When to Use This Skill

Activate this skill when:
- Creating new Terraform or OpenTofu configurations or modules
- Setting up testing infrastructure for IaC code
- Deciding between testing approaches (validate, plan, frameworks)
- Structuring multi-environment deployments
- Implementing CI/CD for infrastructure-as-code
- Reviewing or refactoring existing Terraform/OpenTofu projects
- Choosing between module patterns or state management approaches

Don't use this skill for:
- Basic Terraform/OpenTofu syntax questions (Claude knows this)
- Provider-specific API reference (link to docs instead)
- Cloud platform questions unrelated to Terraform/OpenTofu

@ Core Principles

@ 1. Code Structure Philosophy

Module Hierarchy:

Type; When to Use; Scope

Resource Module; Single logical group of connected resources; VPC + subnets, Security group + rules
Infrastructure Module; Collection of resource modules for a purpose; Multiple resource modules in one region/account
Composition; Complete infrastructure; Spans multiple regions/accounts

Hierarchy: Resource → Resource Module → Infrastructure Module → Composition

Directory Structure:
```
environments/        # Environment-specific configurations
├── prod/
├── staging/
└── dev/

modules/            # Reusable modules
├── networking/
├── compute/
└── data/

examples/           # Module usage examples (also serve as tests)
├── complete/
└── minimal/
```

Key principle from terraform-best-practices.com:
- Separate environments (prod, staging) from modules (reusable components)
- Use examples/ as both documentation and integration test fixtures
- Keep modules small and focused (single responsibility)

For detailed module architecture, see: Code Patterns: Module Types & Hierarchy

@ 2. Naming Conventions

Resources:
```hcl
# Good: Descriptive, contextual
resource "aws_instance" "web_server" { }
resource "aws_s3_bucket" "application_logs" { }

# Good: "this" for singleton resources (only one of that type)
resource "aws_vpc" "this" { }
resource "aws_security_group" "this" { }

# Avoid: Generic names for non-singletons
resource "aws_instance" "main" { }
resource "aws_s3_bucket" "bucket" { }
```

Singleton Resources:

Use "this" when your module creates only one resource of that type:

✅ DO:
```hcl
resource "aws_vpc" "this" {}           # Module creates one VPC
resource "aws_security_group" "this" {}  # Module creates one SG
```

❌ DON'T use "this" for multiple resources:
```hcl
resource "aws_subnet" "this" {}  # If creating multiple subnets
```

Use descriptive names when creating multiple resources of the same type.

Variables:
```hcl
# Prefix with context when needed
var.vpc_cidr_block          # Not just "cidr"
var.database_instance_class # Not just "instance_class"
```

Files:
- main.tf - Primary resources
- variables.tf - Input variables
- outputs.tf - Output values
- versions.tf - Provider versions
- data.tf - Data sources (optional)

@ Testing Strategy Framework

@ Decision Matrix: Which Testing Approach?

Your Situation; Recommended Approach; Tools; Cost

Quick syntax check; Static analysis; terraform validate, fmt; Free
Pre-commit validation; Static + lint; validate, tflint, trivy, checkov; Free
Terraform 1.6+, simple logic; Native test framework; Built-in terraform test; Free-Low
Pre-1.6, or Go expertise; Integration testing; Terratest; Low-Med
Security/compliance focus; Policy as code; OPA, Sentinel; Free
Cost-sensitive workflow; Mock providers (1.7+); Native tests + mocking; Free
Multi-cloud, complex; Full integration; Terratest + real infra; Med-High

@ Testing Pyramid for Infrastructure

```
        /\
       /  \          End-to-End Tests (Expensive)
      /____\         - Full environment deployment
     /      \        - Production-like setup
    /________\
   /          \      Integration Tests (Moderate)
  /____________\     - Module testing in isolation
 /              \    - Real resources in test account
/________________\   Static Analysis (Cheap)
                     - validate, fmt, lint
                     - Security scanning
```

@ Native Test Best Practices (1.6+)

Before generating test code:

1. Validate schemas with Terraform MCP:
   ```
   Search provider docs → Get resource schema → Identify block types
   ```

2. Choose correct command mode:
 - command = plan - Fast, for input validation
 - command = apply - Required for computed values and set-type blocks

3. Handle set-type blocks correctly:
 - Cannot index with [0]
 - Use for expressions to iterate
 - Or use command = apply to materialize

Common patterns:
- S3 encryption rules: set (use for expressions)
- Lifecycle transitions: set (use for expressions)
- IAM policy statements: set (use for expressions)

For detailed testing guides, see:
- Testing Frameworks Guide - Deep dive into static analysis, native tests, and Terratest
- Quick Reference - Decision flowchart and command cheat sheet

@ Code Structure Standards

@ Resource Block Ordering

Strict ordering for consistency:
1. count or for_each FIRST (blank line after)
2. Other arguments
3. tags as last real argument
4. depends_on after tags (if needed)
5. lifecycle at the very end (if needed)

```hcl
# ✅ GOOD - Correct ordering
resource "aws_nat_gateway" "this" {
  count = var.create_nat_gateway ? 1 : 0

  allocation_id = aws_eip.this[0].id
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name = "${var.name}-nat"
  }

  depends_on = [aws_internet_gateway.this]

  lifecycle {
    create_before_destroy = true
  }
}
```

@ Variable Block Ordering

1. description (ALWAYS required)
2. type
3. default
4. validation
5. nullable (when setting to false)

```hcl
variable "environment" {
  description = "Environment name for resource tagging"
  type        = string
  default     = "dev"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be one of: dev, staging, prod."
  }

  nullable = false
}
```

For complete structure guidelines, see: Code Patterns: Block Ordering & Structure

@ Count vs For_Each: When to Use Each

@ Quick Decision Guide

Scenario; Use; Why

Boolean condition (create or don't); count = condition? 1: 0; Simple on/off toggle
Simple numeric replication; count = 3; Fixed number of identical resources
Items may be reordered/removed; for_each = toset(list); Stable resource addresses
Reference by key; for_each = map; Named access to resources
Multiple named resources; for_each; Better maintainability

@ Common Patterns

Boolean conditions:
```hcl
# ✅ GOOD - Boolean condition
resource "aws_nat_gateway" "this" {
  count = var.create_nat_gateway ? 1 : 0
  # ...
}
```

Stable addressing with for_each:
```hcl
# ✅ GOOD - Removing "us-east-1b" only affects that subnet
resource "aws_subnet" "private" {
  for_each = toset(var.availability_zones)

  availability_zone = each.key
  # ...
}

# ❌ BAD - Removing middle AZ recreates all subsequent subnets
resource "aws_subnet" "private" {
  count = length(var.availability_zones)

  availability_zone = var.availability_zones[count.index]
  # ...
}
```

For migration guides and detailed examples, see: Code Patterns: Count vs For_Each

@ Locals for Dependency Management

Use locals to ensure correct resource deletion order:

```hcl
# Problem: Subnets might be deleted after CIDR blocks, causing errors
# Solution: Use try() in locals to hint deletion order

locals {
  # References secondary CIDR first, falling back to VPC
  # Forces Terraform to delete subnets before CIDR association
  vpc_id = try(
    aws_vpc_ipv4_cidr_block_association.this[0].vpc_id,
    aws_vpc.this.id,
    ""
  )
}

resource "aws_vpc" "this" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_vpc_ipv4_cidr_block_association" "this" {
  count = var.add_secondary_cidr ? 1 : 0

  vpc_id     = aws_vpc.this.id
  cidr_block = "10.1.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id     = local.vpc_id  # Uses local, not direct reference
  cidr_block = "10.1.0.0/24"
}
```

Why this matters:
- Prevents deletion errors when destroying infrastructure
- Ensures correct dependency order without explicit depends_on
- Particularly useful for VPC configurations with secondary CIDR blocks

For detailed examples, see: Code Patterns: Locals for Dependency Management

@ Module Development

@ Standard Module Structure

```
my-module/
├── README.md           # Usage documentation
├── main.tf             # Primary resources
├── variables.tf        # Input variables with descriptions
├── outputs.tf          # Output values
├── versions.tf         # Provider version constraints
├── examples/
│   ├── minimal/        # Minimal working example
│   └── complete/       # Full-featured example
└── tests/              # Test files
    └── module_test.tftest.hcl  # Or .go
```

@ Best Practices Summary

Variables:
- ✅ Always include description
- ✅ Use explicit type constraints
- ✅ Provide sensible default values where appropriate
- ✅ Add validation blocks for complex constraints
- ✅ Use sensitive = true for secrets

Outputs:
- ✅ Always include description
- ✅ Mark sensitive outputs with sensitive = true
- ✅ Consider returning objects for related values
- ✅ Document what consumers should do with each output

For detailed module patterns, see:
- Module Patterns Guide - Variable best practices, output design, ✅ DO vs ❌ DON'T patterns
- Quick Reference - Resource naming, variable naming, file organization

@ CI/CD Integration

@ Recommended Workflow Stages

1. Validate - Format check + syntax validation + linting
2. Test - Run automated tests (native or Terratest)
3. Plan - Generate and review execution plan
4. Apply - Execute changes (with approvals for production)

@ Cost Optimization Strategy

1. Use mocking for PR validation (free)
2. Run integration tests only on main branch (controlled cost)
3. Implement auto-cleanup (prevent orphaned resources)
4. Tag all test resources (track spending)

For complete CI/CD templates, see:
- CI/CD Workflows Guide - GitHub Actions, GitLab CI, Atlantis integration, cost optimization
- Quick Reference - Common CI/CD issues and solutions

@ Security & Compliance

@ Essential Security Checks

```bash
# Static security scanning
trivy config .
checkov -d .
```

@ Common Issues to Avoid

❌ Don't:
- Store secrets in variables
- Use default VPC
- Skip encryption
- Open security groups to 0.0.0.0/0

✅ Do:
- Use AWS Secrets Manager / Parameter Store
- Create dedicated VPCs
- Enable encryption at rest
- Use least-privilege security groups

For detailed security guidance, see:
- Security & Compliance Guide - Trivy/Checkov integration, secrets management, state file security, compliance testing

@ Version Management

@ Version Constraint Syntax

```hcl
version = "5.0.0"      # Exact (avoid - inflexible)
version = "~> 5.0"     # Recommended: 5.0.x only
version = ">= 5.0"     # Minimum (risky - breaking changes)
```

@ Strategy by Component

Component; Strategy; Example

Terraform; Pin minor version; required_version = "~> 1.9"
Providers; Pin major version; version = "~> 5.0"
Modules (prod); Pin exact version; version = "5.1.2"
Modules (dev); Allow patch updates; version = "~> 5.1"

@ Update Workflow

```bash
# Lock versions initially
terraform init              # Creates .terraform.lock.hcl

# Update to latest within constraints
terraform init -upgrade     # Updates providers

# Review and test
terraform plan
```

For detailed version management, see: Code Patterns: Version Management

@ Modern Terraform Features (1.0+)

@ Feature Availability by Version

Feature; Version; Use Case

try() function; 0.13+; Safe fallbacks, replaces element(concat())
nullable = false; 1.1+; Prevent null values in variables
moved blocks; 1.1+; Refactor without destroy/recreate
optional() with defaults; 1.3+; Optional object attributes
Native testing; 1.6+; Built-in test framework
Mock providers; 1.7+; Cost-free unit testing
Provider functions; 1.8+; Provider-specific data transformation
Cross-variable validation; 1.9+; Validate relationships between variables
Write-only arguments; 1.11+; Secrets never stored in state

@ Quick Examples

```hcl
# try() - Safe fallbacks (0.13+)
output "sg_id" {
  value = try(aws_security_group.this[0].id, "")
}

# optional() - Optional attributes with defaults (1.3+)
variable "config" {
  type = object({
    name    = string
    timeout = optional(number, 300)  # Default: 300
  })
}

# Cross-variable validation (1.9+)
variable "environment" { type = string }
variable "backup_days" {
  type = number
  validation {
    condition     = var.environment == "prod" ? var.backup_days >= 7 : true
    error_message = "Production requires backup_days >= 7"
  }
}
```

For complete patterns and examples, see: Code Patterns: Modern Terraform Features

@ Version-Specific Guidance

@ Terraform 1.0-1.5
- Use Terratest for testing
- No native testing framework available
- Focus on static analysis and plan validation

@ Terraform 1.6+ / OpenTofu 1.6+
- New: Native terraform test / tofu test command
- Consider migrating from external frameworks for simple tests
- Keep Terratest only for complex integration tests

@ Terraform 1.7+ / OpenTofu 1.7+
- New: Mock providers for unit testing
- Reduce cost by mocking external dependencies
- Use real integration tests for final validation

@ Terraform vs OpenTofu

Both are fully supported by this skill. For licensing, governance, and feature comparison, see Quick Reference: Terraform vs OpenTofu.

@ Detailed Guides

This skill uses progressive disclosure - essential information is in this main file, detailed guides are available when needed:

📚 Reference Files:
- Testing Frameworks - In-depth guide to static analysis, native tests, and Terratest
- Module Patterns - Module structure, variable/output best practices, ✅ DO vs ❌ DON'T patterns
- CI/CD Workflows - GitHub Actions, GitLab CI templates, cost optimization, automated cleanup
- Security & Compliance - Trivy/Checkov integration, secrets management, compliance testing
- Quick Reference - Command cheat sheets, decision flowcharts, troubleshooting guide

How to use: When you need detailed information on a topic, reference the appropriate guide. Claude will load it on demand to provide comprehensive guidance.

@ License

This skill is licensed under the Apache License 2.0. See the LICENSE file for full terms.

Copyright © 2026 Anton Babenko

@ Limitations
- Use this skill only when the task clearly matches the scope described above.
- never treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.

---
> Source: [Regtransfers/agency-agents-mcp](https://github.com/Regtransfers/agency-agents-mcp) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-15 -->

