main.tfcontains data sources only - never resources- Each module must have:
main.tf,outputs.tf,versions.tf,variables.tf - Resources go in separate
.tffiles grouped by service (e.g.,s3.tf,iam.tf) - If >20 resources in one file, split or create submodule
- Use descriptive filenames for resource files
- All variables must have
descriptionandtypedefinitions - Complex types must define complete object structure
- Sort variables alphabetically by name
- Required variables:
role_prefix(default:""),role_permission_boundary_name(default:null) - Never use
regionas variable - usedata.aws_region.current.name - Validate inputs exist using data sources in addition to validation blocks
- Tag all resources that support tags via
tagsvariable - Top-level module must validate
tagscontainsenvironmentkey - Resources without name support: merge tags with
Nameproperty
- Always use
aws_iam_policy_documentdata sources, neverjsonencoded()ed inline JSON - In statements: list
actionsfirst (each on new line), thenresources, thenconditions - Omit
effect = "Allow"(default) - Always include confused deputy conditions in assume role policies such as current account
- Name policy documents: same as role name, or
assume_<service>, or<service>_<resource> - Prefix all role names with
var.role_prefix - Use permission boundary local with ternary operator defaulting to
null - Create separate
aws_iam_role,aws_iam_policy, andaws_iam_role_policy_attachmentresources - Use the name property of the
aws_iam_roleas value for the name property of theaws_iam_policyresource
- Use modern S3 resources (v5+):
aws_s3_bucket_acl,aws_s3_bucket_lifecycle_configuration, etc. - Enable access logging unless explicitly exempted
- Validate logging bucket exists with data source
- Never make buckets public or allow direct website hosting
- Use CloudFront for web access
- Create log groups for resources using CloudWatch logs
- Hardcode retention to 30 days
- Only allow
logs:CreateLogStreamandlogs:PutLogEventspermissions
- Never use
vpc_idas variable - derive from first subnet provided - Validate subnet IDs using regex
- Use
aws_vpc_security_group_ingress_ruleandaws_vpc_security_group_egress_ruleresources - Create minimal access rules only
Run in this order:
terraform fmt -recursive
terraform validate
tflint --recursive- Use empty lines to separate parameter groups
- Avoid >5 spaces before equals sign - add empty line instead
- Each list item on new line with trailing comma
- Run
terraform fmt -recursivebefore all commits