Terraform Coding Standards
This guide covers implementation-level consistency for Terraform and OpenTofu code generation, including naming, typing, iteration, and version discipline.
Naming and Metadata
- Use names that reflect business purpose (
payments_api,audit_kms), not temporary implementation details - Reserve
thisfor real singleton resources only - Centralize tags/labels in
localsand apply consistently - Normalize provider-constrained identifiers before use
Identifier Normalization Pattern
Some resources reject characters valid in domains or service names (e.g., . in RDS identifiers):
locals {
raw_name = "${var.domain}-prod"
normalized_name = regexreplace(lower(local.raw_name), "[^a-z0-9-]", "-")
}
Then use local.normalized_name for fields like identifier and final_snapshot_identifier.
Resource Block Ordering
Preferred order inside resource blocks:
- Identity/core arguments
- Behavior/config arguments
- Nested blocks
- Tags/labels
- Lifecycle/meta-arguments
This keeps diffs predictable and reviewable.
Variable Contract Style
Variable attribute order:
descriptiontypedefault(if used)nullablesensitivevalidation
Prefer explicit objects with optional() over untyped maps for long-lived module contracts.
Iteration and Identity
| Pattern | Use When |
|---|---|
count |
Optional singleton toggles (0 or 1) |
for_each |
Any collection with stable logical identities |
Never use list index as long-lived identity key.
Set-Type Handling
Rules to avoid ordering bugs and test failures:
- Never index sets directly; convert with
sort(tolist(...))when order matters - Use
for_eachwithtoset(...)only when identity is the value itself - For stable diff output, transform sets to sorted lists in outputs
- In tests, prefer
contains()or set equality over positional assertions
Outputs
- Expose only stable interfaces needed by consumers
- Mark secret-bearing outputs as
sensitive = true - Avoid dumping entire provider objects
Version and Lock Discipline
- Set runtime floor in
required_version - Bound provider and module versions
- Commit lockfile changes intentionally
- Keep upgrade PRs separate from functional changes where possible
Terraform Feature Guard Table
Use this table when deciding what to emit for a given runtime floor. Maps features to their minimum version and the specific LLM error pattern for each.
| Feature | Min Version | LLM Error Pattern |
|---|---|---|
moved blocks |
1.1+ | Omitted during refactor, causing destroy/create |
for_each over count |
0.12+ | Model defaults to count for every collection |
write_only arguments |
1.11+ | Model uses sensitive and assumes state is safe |
optional() defaults |
1.3+ | Model emits wrapper variables and loose maps |
| Cross-variable validation | 1.9+ | Model pushes checks into postconditions only |
Declarative import blocks |
1.5+ | Model recommends ad-hoc CLI import only |
check blocks |
1.5+ | Model ignores runtime assertions entirely |
removed blocks |
1.7+ | Model deletes resources with no lifecycle transition |
| Provider-defined functions | 1.8+ | Model overuses data sources for transformations |
If target runtime is below a feature floor, emit fallback guidance explicitly.
Review Checklist
- Typed inputs and validations present
- Iteration model chosen for address stability
- No plaintext secret defaults
- Version constraints and lockfile strategy are explicit
- Migration-sensitive changes include
moved/importplan