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 |