# Iac Terraform Guardian

> Write and change infrastructure-as-code (Terraform/OpenTonu) safely — state integrity, plan review, modularity, and no drift or destroy-surprises. Use before authoring or modifying any IaC that manages real cloud resources. Enforces remote locked state, read-the-plan discipline, module boundaries, tagging, and the destroy/replace guardrails that prevent accidental data loss.

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

---


# iac-terraform-guardian

Infrastructure-as-code turns a typo into a deleted database. This skill makes IaC changes boring and reversible: state is remote and locked, every apply is preceded by a *read* plan, modules have clean boundaries, and the operations that can destroy data are gated behind explicit review. Terraform/OpenTofu idiom; the principles port to Pulumi/CDK.

## Use BEFORE
- Authoring new IaC for real cloud resources.
- Any change to existing IaC — especially anything touching stateful resources (databases, disks, buckets).
- Refactoring modules or moving resources between states.

## State is sacred
- **Remote backend with locking** (S3+DynamoDB, GCS, Terraform Cloud). Never local state for shared infra — two engineers applying against local state corrupt each other's world.
- **Locking on** so concurrent applies can't race. If a lock is stuck, understand why before force-unlocking — a force-unlock during an in-flight apply corrupts state.
- **State contains secrets** (DB passwords, keys are stored in plaintext in state). Encrypt the backend at rest, restrict access, and never commit state or `terraform.tfstate.backup` to git.
- **Never hand-edit state.** Use `terraform state mv`/`import`/`rm` for surgery; a hand-edited state file is how you get a resource Terraform thinks exists twice.
- **Import, don't recreate.** Adopt existing resources with `import` rather than letting Terraform destroy-and-recreate them.

## Read the plan — every time
`plan` is the single most important safety mechanism. Read it, don't skim it.
- **Count the destroys.** `Plan: X to add, Y to change, Z to destroy.` Any non-zero `destroy` on a stateful resource is a stop-and-review. A change that unexpectedly wants to destroy a database is almost always a mistake (a forced replacement).
- **Watch for `-/+` (replace).** A change to an immutable attribute forces destroy-then-create. On a stateful resource that means data loss. Look for `# forces replacement` in the plan and confirm it's intended.
- **Apply exactly what you planned.** Save the plan (`terraform plan -out=tfplan`) and `apply tfplan` so the applied change is the reviewed one — not a fresh plan that drifted since review.
- **Plan in CI on PRs**, post the plan output for review; apply only after merge, from CI, with approval on production.

## Guardrails against destroy-surprises
- `lifecycle { prevent_destroy = true }` on databases, disks, and anything holding data you can't lose. It turns an accidental destroy into a hard error.
- `create_before_destroy` for resources that must not have a gap (so the replacement exists before the old one is torn down).
- Deletion protection at the provider level (RDS deletion protection, S3 bucket with versioning + MFA delete) as defense in depth — don't rely on Terraform alone.

## Modularity and structure
- **Modules have a clear interface:** documented inputs (variables with types + descriptions + validation), documented outputs, no hidden reliance on provider config leaking in. A module is a function — typed in, typed out.
- **Separate state per environment** (dev/staging/prod) via workspaces or separate state files, so a dev apply can never touch prod. Same modules, different variable values.
- **Don't over-modularize.** A module per resource is noise; module around a *meaningful unit* (a service's full footprint, a network). Duplication is cheaper than the wrong abstraction.
- **Pin provider and module versions** (`required_providers` with `~>` constraints, module `version =`). Unpinned providers mean an unrelated apply suddenly upgrades your provider and changes plans.

## Hygiene
- **Tag everything** — owner, environment, cost-center, managed-by=terraform. Untagged resources are unattributable cost and orphan risk. Enforce via a default_tags/provider block.
- **No secrets in `.tf` or `.tfvars` committed to git.** Source them from a secrets manager / environment / a vault provider. (See secrets-management.)
- **Format and validate** (`fmt`, `validate`, `tflint`, a policy check like OPA/Sentinel/`tfsec`) in CI.
- **Detect drift** on a schedule (`plan` against prod) — someone clicking in the console silently diverges reality from code.

## Procedure
1. Confirm remote, locked, encrypted backend; separate state for the target environment.
2. Author/change with pinned providers + typed module interfaces; add `prevent_destroy` to stateful resources.
3. `fmt` → `validate` → `tflint`/`tfsec` → `plan -out`.
4. **Read the plan**: reconcile every add/change/destroy/replace with intent; stop on any unexpected destroy or replace of stateful infra.
5. Review the saved plan (in CI/PR); get approval for production.
6. `apply` the saved plan. Verify resources and re-plan to confirm zero diff (no drift).
7. Confirm tags, and that no secret landed in state-in-git or `.tfvars`-in-git.

## Definition of done
- State remote, locked, encrypted, per-environment, never in git.
- Providers/modules pinned; modules have typed, documented interfaces.
- The saved plan was read and reviewed; no unintended destroy/replace of stateful resources.
- `prevent_destroy`/deletion protection on data-bearing resources.
- Everything tagged; no secrets in committed IaC; `fmt`/`validate`/policy checks green in CI.

