# Terraform Do and Don't Checklist A fast reference checklist for safe Terraform and OpenTofu code generation. Use this for quick reviews. ## Identity and Iteration | Do | Don't | |---|---| | Use `for_each` with stable, business-meaningful keys | Use list index as long-lived identity | | Keep identity keys separate from mutable attributes | Derive identity from a computed attribute | | Add `moved` blocks when renaming resources or modules | Delete/rename addresses without an explicit migration plan | ## Secrets and Sensitive Data | Do | Don't | |---|---| | Mark secret outputs as `sensitive = true` | Put secrets in `default` values or `.tfvars` committed to VCS | | Use secret managers and data sources for runtime injection | Echo secrets in provisioner commands | | Avoid logging sensitive values in `locals` or `output` | Rely on `sensitive` alone to protect state contents | ## State Boundaries and Blast Radius | Do | Don't | |---|---| | Keep production in isolated state backends or workspaces | Mix unrelated systems in a single root state | | Split large stacks by lifecycle and ownership | Apply directly to production from unreviewed branches | | Use environment protection and approvals for apply | Use one monolithic stack for all environments | ## Module Contracts | Do | Don't | |---|---| | Expose typed inputs and explicit outputs | Accept untyped `map(any)` for core interfaces | | Use `optional()` for evolution-friendly contracts | Expose entire provider objects as outputs | | Validate invariants with `validation` and `precondition` | Push environment-specific policy into primitive modules | ## Providers and Versions | Do | Don't | |---|---| | Pin runtime and providers with bounded constraints | Float provider versions | | Commit `.terraform.lock.hcl` intentionally | Rely on implicit provider inheritance in multi-region setups | | Pass provider aliases explicitly to child modules | Mix upgrades with functional changes in the same PR | ## Data Sources and Dependencies | Do | Don't | |---|---| | Use data sources for read-only integration | Use `depends_on` to paper over missing interfaces | | Model dependencies via input/output wiring | Use data sources for identity fields that can change | | Keep `depends_on` for real ordering requirements only | Create hidden ordering between unrelated resources | ## CI/CD and Policy | Do | Don't | |---|---| | Separate plan and apply | Allow direct apply from arbitrary branches | | Keep an auditable reviewed plan artifact | Skip policy checks for production changes | | Run policy and cost checks on every plan | Delete plan artifacts before approval | ## Testing | Do | Don't | |---|---| | Run `terraform test` / `tofu test` for module-level checks | Rely on plan-only validation for runtime-only attributes | | Use Terratest for workflow or integration validation | Run destructive tests without isolation and cleanup | | Tier tests by risk and cost | Treat mocked provider tests as full integration coverage | ## Migration and Refactors | Do | Don't | |---|---| | Include `moved` or `import` strategy in the same change | Rename resources without preserving state identity | | Run a reviewed plan before any apply | Apply refactors without plan review | | Document rollback steps for destructive changes | Remove resources without lifecycle transition |