- Run terraform fmt on all files — enforce consistent formatting in CI
- Use snake_case for all resource names, variables, outputs, and locals
- Organize files: main.tf (resources), variables.tf (inputs), outputs.tf (exports), versions.tf (providers)
- Use meaningful resource names that describe purpose: aws_s3_bucket.user_uploads not aws_s3_bucket.bucket1
- Pin provider versions with ~> constraint: required_providers { aws = { version = "~> 5.0" } }
- Run terraform fmt -recursive on all files; enforce in CI with a formatting check step
- Use snake_case for all identifiers: resource names, variables, outputs, locals, module names
- Standard file layout: main.tf, variables.tf, outputs.tf, versions.tf, locals.tf, data.tf
- Use descriptive resource names: aws_s3_bucket.user_uploads_bucket not aws_s3_bucket.this or aws_s3_bucket.bucket
- Pin provider versions with ~> constraint for automatic minor/patch updates: version = "~> 5.0"
- Use variable descriptions and type constraints for all input variables — self-documenting infrastructure
- Add validation blocks on variables for business rules: CIDR ranges, naming patterns, allowed values
- Group related resources in the same file; split into separate files when a file exceeds ~200 lines
- Use locals for computed values and to reduce repetition — never repeat the same expression twice
- Add lifecycle blocks explicitly when needed: prevent_destroy for databases, ignore_changes for auto-managed fields
- Comment complex logic with # explanations; avoid obvious comments that restate the resource type
- Use terraform-docs to auto-generate README.md documentation for modules