Skip to content
Development
Skill

/ia-terraform

Terraform and OpenTofu configuration, modules, testing, state management, and HCL review. Use when working with Terraform, OpenTofu, HCL, tfvars, tftest, state migration, or IaC patterns.

From plugin
whetstone
3333 skills19 agents38 commands1 MCP
Install
$ npx -y skills add iliaal/whetstone --skill ia-terraform --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/ia-terraform

Context preview

The summary Claude sees to decide when to auto-load this skill.

Terraform and OpenTofu configuration, modules, testing, state management, and HCL review. Use when working with Terraform, OpenTofu, HCL, tfvars, tftest, state migration, or IaC patterns.

SKILL.md

ia-terraform.SKILL.md
name: ia-terraform
class: language
description: >-
  Terraform and OpenTofu configuration, modules, testing, state management, and
  HCL review. Use when working with Terraform, OpenTofu, HCL, tfvars, tftest,
  state migration, or IaC patterns.
paths: "**/*.tf,**/*.tfvars"

Terraform & OpenTofu

Working rules

  • Preserve state and resource addresses during refactoring; inspect the plan for unintended replacement.
  • Separate plan-only checks from apply-mode tests that create real infrastructure and incur cost.

File Organization & Naming

| File | Purpose | |------|---------| | `terraform.tf` | Terraform + provider version requirements | | `providers.tf` | Provider configurations | | `main.tf` | Primary resources and data sources | | `variables.tf` | Input variables (alphabetical) | | `outputs.tf` | Output values (alphabetical) | | `locals.tf` | Local values |

  • Lowercase with underscores: `web_api`, not `webAPI` or `web-api`
  • Descriptive nouns excluding resource type: `aws_instance.web_api` not `aws_instance.web_api_instance`
  • Singular, not plural
  • `this` for singleton resources (one of that type per module)
  • Contextual variable prefixes: `vpc_cidr_block` not `cidr`

Block Ordering

**Resources:** `count`/`for_each` (blank line after) → arguments → nested blocks → `tags` → `depends_on` → `lifecycle` (last)

**Variables:** `description` → `type` → `default` → `validation` → `nullable`

Every variable needs `type` + `description`. Every output needs `description`. Mark secrets `sensitive = true`.

Module Structure

| Type | Scope | Example | |------|-------|---------| | Resource Module | Single logical group | VPC + subnets, SG + rules | | Infrastructure Module | Collection of resource modules | Networking + compute for one region | | Composition | Complete infrastructure | Spans regions/accounts |

module-name/
├── main.tf, variables.tf, outputs.tf, versions.tf
├── examples/
│   ├── minimal/
│   └── complete/
└── tests/
    └── defaults.tftest.hcl

Keep modules small (single responsibility). `examples/` double as documentation and integration test fixtures. Semantic versioning for all published modules.

count vs for_each

| Scenario | Use | |----------|-----| | Boolean toggle (create or skip) | `count = condition ? 1 : 0` | | Named/keyed items that may reorder | `for_each = toset(list)` or `map` | | Fixed identical replicas | `count = N` |

Default to `for_each` -- removing a middle item from a `count` list recreates all subsequent resources. Use `count` only for boolean conditionals or truly identical replicas.

Version Pinning

| Component | Strategy | Example | |-----------|----------|---------| | Terraform | Pin minor | `required_version = "~> 1.9.0"` | | Providers | Pin major | `version = "~> 5.0"` | | Modules (prod) | Pin exact | `version = "5.1.2"` | | Modules (dev) | Allow patch | `version = "~> 5.1.0"` |

Key modern features: `moved` blocks (1.1+), `optional()` with defaults (1.3+), native testing (1.6+), mock providers (1.7+), cross-variable validation (1.9+), write-only arguments (1.11+). Stacks (HCP -- check current release status): orchestrates multiple configs as a single deployment unit -- evaluate for multi-environment patterns.

State & Security

  • Remote backend with locking: S3 with `use_lockfile = true` (1.10+), Azure Blob, GCS, or Terraform Cloud. Never local state for shared infrastructure. DynamoDB-based S3 locking (`dynamodb_table`) is deprecated and slated for removal -- prefer `use_lockfile`; both may be set at once while migrating an existing table off.
  • OpenTofu-only: `terraform { encryption { key_provider "pbkdf2" "k" {...} method "aes_gcm" "m" { keys = key_provider.pbkdf2.k } state { method = method.aes_gcm.m } plan { method = method.aes_gcm.m } } }` encrypts state and plan files client-side (or via `TF_ENCRYPTION`). Roll out with a `fallback { method = method.unencrypted.x }` so existing plaintext state still loads, and never rename a key provider or method without a `fallback` block. OpenTofu also accepts `var.*`/`local.*` in `backend {}` arguments and in module `source`/`version` (resolved at `init`; no state or provider-function references); the same HCL is a hard error in Terraform ("A backend block cannot refer to named values").
  • Encrypt state at rest. Never commit `.tfstate`, `.terraform/`, or `*.tfplan`. Always commit `.terraform.lock.hcl`.
  • `default_tags` on provider for consistent resource tagging.
  • Encryption at rest on all storage. Private networking by default -- public access is opt-in.
  • Least-privilege security groups. No `0.0.0.0/0` ingress without explicit justification.
  • Never hardcode credentials -- use assume_role, OIDC, or secrets managers.
  • Pre-commit: auto-format first (`terraform fmt -recursive` -- rewrites files), then verify (`terraform validate && tflint && trivy config .`)
  • Use `moved` blocks with `from` and `to` addresses for refactoring resource names/modules without destroy-recreate. Retain historical moves for downstream upgrades; remove only after every affected state has migrated, or as an explicitly breaking module release.
  • `lifecycle { ignore_changes = [attr] }` suppresses **updates only**, and it substitutes the prior state value at plan time -- on the *first* plan after the config change, with no "first apply" exception. Two consequences reviewers get backwards: (1) on an already-provisioned resource the literal in the config is never written, and `ForceNew` never fires because `ignore_changes` erased the diff before replacement is evaluated -- so a change that replaces a committed value with a placeholder scrubs the repository and leaves the remote value live; (2) `ignore_changes` does not apply on create, so any later `-replace`, taint, `state rm` + re-add, or manual deletion re-seeds the placeholder over a value that was set out of band. Keep only the container resource in configuration and provision the value entirely out of band, or state the restore ste
Read more
Ships withwhetstone

A Claude Code plugin that makes AI coding agents follow engineering discipline. Plan before coding. Verify before claiming done. Find root cause before patching. Review before merge. Skills activate based on file type and task signals, not manual toggling.

Get the whole plugin

Other skills on whetstone.