Terraform infrastructure as code best practices
Add this skill
npx mdskills install sickn33/terraform-skillComprehensive IaC guidance with clear hierarchies, testing strategies, and production patterns
1---2name: terraform-skill3description: "Terraform infrastructure as code best practices"4license: Apache-2.05metadata:6author: "Anton Babenko"7version: 1.5.08source: "https://github.com/antonbabenko/terraform-skill"9risk: safe10---11# Terraform Skill for Claude1213Comprehensive Terraform and OpenTofu guidance covering testing, modules, CI/CD, and production patterns. Based on terraform-best-practices.com and enterprise experience.1415## When to Use This Skill1617**Activate this skill when:**18- Creating new Terraform or OpenTofu configurations or modules19- Setting up testing infrastructure for IaC code20- Deciding between testing approaches (validate, plan, frameworks)21- Structuring multi-environment deployments22- Implementing CI/CD for infrastructure-as-code23- Reviewing or refactoring existing Terraform/OpenTofu projects24- Choosing between module patterns or state management approaches2526**Don't use this skill for:**27- Basic Terraform/OpenTofu syntax questions (Claude knows this)28- Provider-specific API reference (link to docs instead)29- Cloud platform questions unrelated to Terraform/OpenTofu3031## Core Principles3233### 1. Code Structure Philosophy3435**Module Hierarchy:**3637| Type | When to Use | Scope |38|------|-------------|-------|39| **Resource Module** | Single logical group of connected resources | VPC + subnets, Security group + rules |40| **Infrastructure Module** | Collection of resource modules for a purpose | Multiple resource modules in one region/account |41| **Composition** | Complete infrastructure | Spans multiple regions/accounts |4243**Hierarchy:** Resource → Resource Module → Infrastructure Module → Composition4445**Directory Structure:**46```47environments/ # Environment-specific configurations48├── prod/49├── staging/50└── dev/5152modules/ # Reusable modules53├── networking/54├── compute/55└── data/5657examples/ # Module usage examples (also serve as tests)58├── complete/59└── minimal/60```6162**Key principle from terraform-best-practices.com:**63- Separate **environments** (prod, staging) from **modules** (reusable components)64- Use **examples/** as both documentation and integration test fixtures65- Keep modules small and focused (single responsibility)6667**For detailed module architecture, see:** [Code Patterns: Module Types & Hierarchy](references/code-patterns.md)6869### 2. Naming Conventions7071**Resources:**72```hcl73# Good: Descriptive, contextual74resource "aws_instance" "web_server" { }75resource "aws_s3_bucket" "application_logs" { }7677# Good: "this" for singleton resources (only one of that type)78resource "aws_vpc" "this" { }79resource "aws_security_group" "this" { }8081# Avoid: Generic names for non-singletons82resource "aws_instance" "main" { }83resource "aws_s3_bucket" "bucket" { }84```8586**Singleton Resources:**8788Use `"this"` when your module creates only one resource of that type:8990✅ DO:91```hcl92resource "aws_vpc" "this" {} # Module creates one VPC93resource "aws_security_group" "this" {} # Module creates one SG94```9596❌ DON'T use "this" for multiple resources:97```hcl98resource "aws_subnet" "this" {} # If creating multiple subnets99```100101Use descriptive names when creating multiple resources of the same type.102103**Variables:**104```hcl105# Prefix with context when needed106var.vpc_cidr_block # Not just "cidr"107var.database_instance_class # Not just "instance_class"108```109110**Files:**111- `main.tf` - Primary resources112- `variables.tf` - Input variables113- `outputs.tf` - Output values114- `versions.tf` - Provider versions115- `data.tf` - Data sources (optional)116117## Testing Strategy Framework118119### Decision Matrix: Which Testing Approach?120121| Your Situation | Recommended Approach | Tools | Cost |122|----------------|---------------------|-------|------|123| **Quick syntax check** | Static analysis | `terraform validate`, `fmt` | Free |124| **Pre-commit validation** | Static + lint | `validate`, `tflint`, `trivy`, `checkov` | Free |125| **Terraform 1.6+, simple logic** | Native test framework | Built-in `terraform test` | Free-Low |126| **Pre-1.6, or Go expertise** | Integration testing | Terratest | Low-Med |127| **Security/compliance focus** | Policy as code | OPA, Sentinel | Free |128| **Cost-sensitive workflow** | Mock providers (1.7+) | Native tests + mocking | Free |129| **Multi-cloud, complex** | Full integration | Terratest + real infra | Med-High |130131### Testing Pyramid for Infrastructure132133```134 /\135 / \ End-to-End Tests (Expensive)136 /____\ - Full environment deployment137 / \ - Production-like setup138 /________\139 / \ Integration Tests (Moderate)140 /____________\ - Module testing in isolation141 / \ - Real resources in test account142/________________\ Static Analysis (Cheap)143 - validate, fmt, lint144 - Security scanning145```146147### Native Test Best Practices (1.6+)148149**Before generating test code:**1501511. **Validate schemas with Terraform MCP:**152 ```153 Search provider docs → Get resource schema → Identify block types154 ```1551562. **Choose correct command mode:**157 - `command = plan` - Fast, for input validation158 - `command = apply` - Required for computed values and set-type blocks1591603. **Handle set-type blocks correctly:**161 - Cannot index with `[0]`162 - Use `for` expressions to iterate163 - Or use `command = apply` to materialize164165**Common patterns:**166- S3 encryption rules: **set** (use for expressions)167- Lifecycle transitions: **set** (use for expressions)168- IAM policy statements: **set** (use for expressions)169170**For detailed testing guides, see:**171- **[Testing Frameworks Guide](references/testing-frameworks.md)** - Deep dive into static analysis, native tests, and Terratest172- **[Quick Reference](references/quick-reference.md#testing-approach-selection)** - Decision flowchart and command cheat sheet173174## Code Structure Standards175176### Resource Block Ordering177178**Strict ordering for consistency:**1791. `count` or `for_each` FIRST (blank line after)1802. Other arguments1813. `tags` as last real argument1824. `depends_on` after tags (if needed)1835. `lifecycle` at the very end (if needed)184185```hcl186# ✅ GOOD - Correct ordering187resource "aws_nat_gateway" "this" {188 count = var.create_nat_gateway ? 1 : 0189190 allocation_id = aws_eip.this[0].id191 subnet_id = aws_subnet.public[0].id192193 tags = {194 Name = "${var.name}-nat"195 }196197 depends_on = [aws_internet_gateway.this]198199 lifecycle {200 create_before_destroy = true201 }202}203```204205### Variable Block Ordering2062071. `description` (ALWAYS required)2082. `type`2093. `default`2104. `validation`2115. `nullable` (when setting to false)212213```hcl214variable "environment" {215 description = "Environment name for resource tagging"216 type = string217 default = "dev"218219 validation {220 condition = contains(["dev", "staging", "prod"], var.environment)221 error_message = "Environment must be one of: dev, staging, prod."222 }223224 nullable = false225}226```227228**For complete structure guidelines, see:** [Code Patterns: Block Ordering & Structure](references/code-patterns.md#block-ordering--structure)229230## Count vs For_Each: When to Use Each231232### Quick Decision Guide233234| Scenario | Use | Why |235|----------|-----|-----|236| Boolean condition (create or don't) | `count = condition ? 1 : 0` | Simple on/off toggle |237| Simple numeric replication | `count = 3` | Fixed number of identical resources |238| Items may be reordered/removed | `for_each = toset(list)` | Stable resource addresses |239| Reference by key | `for_each = map` | Named access to resources |240| Multiple named resources | `for_each` | Better maintainability |241242### Common Patterns243244**Boolean conditions:**245```hcl246# ✅ GOOD - Boolean condition247resource "aws_nat_gateway" "this" {248 count = var.create_nat_gateway ? 1 : 0249 # ...250}251```252253**Stable addressing with for_each:**254```hcl255# ✅ GOOD - Removing "us-east-1b" only affects that subnet256resource "aws_subnet" "private" {257 for_each = toset(var.availability_zones)258259 availability_zone = each.key260 # ...261}262263# ❌ BAD - Removing middle AZ recreates all subsequent subnets264resource "aws_subnet" "private" {265 count = length(var.availability_zones)266267 availability_zone = var.availability_zones[count.index]268 # ...269}270```271272**For migration guides and detailed examples, see:** [Code Patterns: Count vs For_Each](references/code-patterns.md#count-vs-for_each-deep-dive)273274## Locals for Dependency Management275276**Use locals to ensure correct resource deletion order:**277278```hcl279# Problem: Subnets might be deleted after CIDR blocks, causing errors280# Solution: Use try() in locals to hint deletion order281282locals {283 # References secondary CIDR first, falling back to VPC284 # Forces Terraform to delete subnets before CIDR association285 vpc_id = try(286 aws_vpc_ipv4_cidr_block_association.this[0].vpc_id,287 aws_vpc.this.id,288 ""289 )290}291292resource "aws_vpc" "this" {293 cidr_block = "10.0.0.0/16"294}295296resource "aws_vpc_ipv4_cidr_block_association" "this" {297 count = var.add_secondary_cidr ? 1 : 0298299 vpc_id = aws_vpc.this.id300 cidr_block = "10.1.0.0/16"301}302303resource "aws_subnet" "public" {304 vpc_id = local.vpc_id # Uses local, not direct reference305 cidr_block = "10.1.0.0/24"306}307```308309**Why this matters:**310- Prevents deletion errors when destroying infrastructure311- Ensures correct dependency order without explicit `depends_on`312- Particularly useful for VPC configurations with secondary CIDR blocks313314**For detailed examples, see:** [Code Patterns: Locals for Dependency Management](references/code-patterns.md#locals-for-dependency-management)315316## Module Development317318### Standard Module Structure319320```321my-module/322├── README.md # Usage documentation323├── main.tf # Primary resources324├── variables.tf # Input variables with descriptions325├── outputs.tf # Output values326├── versions.tf # Provider version constraints327├── examples/328│ ├── minimal/ # Minimal working example329│ └── complete/ # Full-featured example330└── tests/ # Test files331 └── module_test.tftest.hcl # Or .go332```333334### Best Practices Summary335336**Variables:**337- ✅ Always include `description`338- ✅ Use explicit `type` constraints339- ✅ Provide sensible `default` values where appropriate340- ✅ Add `validation` blocks for complex constraints341- ✅ Use `sensitive = true` for secrets342343**Outputs:**344- ✅ Always include `description`345- ✅ Mark sensitive outputs with `sensitive = true`346- ✅ Consider returning objects for related values347- ✅ Document what consumers should do with each output348349**For detailed module patterns, see:**350- **[Module Patterns Guide](references/module-patterns.md)** - Variable best practices, output design, ✅ DO vs ❌ DON'T patterns351- **[Quick Reference](references/quick-reference.md#common-patterns)** - Resource naming, variable naming, file organization352353## CI/CD Integration354355### Recommended Workflow Stages3563571. **Validate** - Format check + syntax validation + linting3582. **Test** - Run automated tests (native or Terratest)3593. **Plan** - Generate and review execution plan3604. **Apply** - Execute changes (with approvals for production)361362### Cost Optimization Strategy3633641. **Use mocking for PR validation** (free)3652. **Run integration tests only on main branch** (controlled cost)3663. **Implement auto-cleanup** (prevent orphaned resources)3674. **Tag all test resources** (track spending)368369**For complete CI/CD templates, see:**370- **[CI/CD Workflows Guide](references/ci-cd-workflows.md)** - GitHub Actions, GitLab CI, Atlantis integration, cost optimization371- **[Quick Reference](references/quick-reference.md#troubleshooting-guide)** - Common CI/CD issues and solutions372373## Security & Compliance374375### Essential Security Checks376377```bash378# Static security scanning379trivy config .380checkov -d .381```382383### Common Issues to Avoid384385❌ **Don't:**386- Store secrets in variables387- Use default VPC388- Skip encryption389- Open security groups to 0.0.0.0/0390391✅ **Do:**392- Use AWS Secrets Manager / Parameter Store393- Create dedicated VPCs394- Enable encryption at rest395- Use least-privilege security groups396397**For detailed security guidance, see:**398- **[Security & Compliance Guide](references/security-compliance.md)** - Trivy/Checkov integration, secrets management, state file security, compliance testing399400## Version Management401402### Version Constraint Syntax403404```hcl405version = "5.0.0" # Exact (avoid - inflexible)406version = "~> 5.0" # Recommended: 5.0.x only407version = ">= 5.0" # Minimum (risky - breaking changes)408```409410### Strategy by Component411412| Component | Strategy | Example |413|-----------|----------|---------|414| **Terraform** | Pin minor version | `required_version = "~> 1.9"` |415| **Providers** | Pin major version | `version = "~> 5.0"` |416| **Modules (prod)** | Pin exact version | `version = "5.1.2"` |417| **Modules (dev)** | Allow patch updates | `version = "~> 5.1"` |418419### Update Workflow420421```bash422# Lock versions initially423terraform init # Creates .terraform.lock.hcl424425# Update to latest within constraints426terraform init -upgrade # Updates providers427428# Review and test429terraform plan430```431432**For detailed version management, see:** [Code Patterns: Version Management](references/code-patterns.md#version-management)433434## Modern Terraform Features (1.0+)435436### Feature Availability by Version437438| Feature | Version | Use Case |439|---------|---------|----------|440| `try()` function | 0.13+ | Safe fallbacks, replaces `element(concat())` |441| `nullable = false` | 1.1+ | Prevent null values in variables |442| `moved` blocks | 1.1+ | Refactor without destroy/recreate |443| `optional()` with defaults | 1.3+ | Optional object attributes |444| Native testing | 1.6+ | Built-in test framework |445| Mock providers | 1.7+ | Cost-free unit testing |446| Provider functions | 1.8+ | Provider-specific data transformation |447| Cross-variable validation | 1.9+ | Validate relationships between variables |448| Write-only arguments | 1.11+ | Secrets never stored in state |449450### Quick Examples451452```hcl453# try() - Safe fallbacks (0.13+)454output "sg_id" {455 value = try(aws_security_group.this[0].id, "")456}457458# optional() - Optional attributes with defaults (1.3+)459variable "config" {460 type = object({461 name = string462 timeout = optional(number, 300) # Default: 300463 })464}465466# Cross-variable validation (1.9+)467variable "environment" { type = string }468variable "backup_days" {469 type = number470 validation {471 condition = var.environment == "prod" ? var.backup_days >= 7 : true472 error_message = "Production requires backup_days >= 7"473 }474}475```476477**For complete patterns and examples, see:** [Code Patterns: Modern Terraform Features](references/code-patterns.md#modern-terraform-features-10)478479## Version-Specific Guidance480481### Terraform 1.0-1.5482- Use Terratest for testing483- No native testing framework available484- Focus on static analysis and plan validation485486### Terraform 1.6+ / OpenTofu 1.6+487- **New:** Native `terraform test` / `tofu test` command488- Consider migrating from external frameworks for simple tests489- Keep Terratest only for complex integration tests490491### Terraform 1.7+ / OpenTofu 1.7+492- **New:** Mock providers for unit testing493- Reduce cost by mocking external dependencies494- Use real integration tests for final validation495496### Terraform vs OpenTofu497498Both are fully supported by this skill. For licensing, governance, and feature comparison, see [Quick Reference: Terraform vs OpenTofu](references/quick-reference.md#terraform-vs-opentofu-comparison).499500## Detailed Guides501502This skill uses **progressive disclosure** - essential information is in this main file, detailed guides are available when needed:503504📚 **Reference Files:**505- **[Testing Frameworks](references/testing-frameworks.md)** - In-depth guide to static analysis, native tests, and Terratest506- **[Module Patterns](references/module-patterns.md)** - Module structure, variable/output best practices, ✅ DO vs ❌ DON'T patterns507- **[CI/CD Workflows](references/ci-cd-workflows.md)** - GitHub Actions, GitLab CI templates, cost optimization, automated cleanup508- **[Security & Compliance](references/security-compliance.md)** - Trivy/Checkov integration, secrets management, compliance testing509- **[Quick Reference](references/quick-reference.md)** - Command cheat sheets, decision flowcharts, troubleshooting guide510511**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.512513## License514515This skill is licensed under the **Apache License 2.0**. See the LICENSE file for full terms.516517**Copyright © 2026 Anton Babenko**518
Full transparency — inspect the skill content before installing.