{One-sentence description. Example: "AWS infrastructure for the Acme SaaS platform."} IaC: Terraform {1.6+} | Cloud: AWS ({us-east-1}) State backend: {S3 + DynamoDB locking} Environments: {dev, staging, prod}
# Init and plan (always specify environment)
terraform -chdir=envs/{dev} init
terraform -chdir=envs/{dev} plan -out=plan.tfplan
terraform -chdir=envs/{dev} apply plan.tfplan
# Targeted operations (use sparingly)
terraform plan -target=module.{name}
terraform apply -target=module.{name}
# State inspection
terraform state list
terraform state show module.{name}.aws_instance.{resource}
# Formatting and validation
terraform fmt -recursive
terraform validate
{tflint --recursive} # Linting
# Cost estimation (optional)
{infracost breakdown --path envs/dev}modules/ # Reusable Terraform modules
├── networking/ # VPC, subnets, NAT, security groups
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
├── compute/ # EC2, ASG, Launch Templates
├── database/ # RDS, ElastiCache, DynamoDB
├── storage/ # S3, EFS
├── iam/ # IAM roles, policies, instance profiles
└── monitoring/ # CloudWatch, SNS, alarms
envs/ # Environment-specific configurations
├── dev/
│ ├── main.tf # Module calls with dev values
│ ├── variables.tf # Variable declarations
│ ├── terraform.tfvars # Dev-specific values
│ ├── backend.tf # S3 backend config for dev
│ └── outputs.tf
├── staging/
└── prod/
- Every module has:
main.tf,variables.tf,outputs.tf - All variables must have
descriptionandtype - Sensitive variables: mark with
sensitive = true - Use
localsfor computed values and repeated expressions - Module sources: relative paths for local (
../../modules/networking) - Pin module versions if using registry:
version = "~> 3.0"
# Standard module call pattern
module "vpc" {
source = "../../modules/networking"
environment = var.environment
project_name = var.project_name
vpc_cidr = var.vpc_cidr
azs = ["us-east-1a", "us-east-1b"]
tags = local.common_tags
}- Backend: S3 bucket with versioning + DynamoDB table for locking
- One state file per environment (dev/staging/prod are isolated)
- Never run
terraform statewrite commands without team approval - Use
terraform importto bring existing resources under management - State is source of truth — never modify AWS resources manually in console
- Resources:
{project}-{environment}-{component}-{resource}- Example:
acme-prod-api-alb,acme-dev-db-primary
- Example:
- Terraform names:
snake_casefor resources, variables, outputs - Tags (required on all resources):
Project,Environment,ManagedBy = "terraform",Owner
- Variables: descriptive names, no abbreviations (
database_instance_classnotdb_cls)
- No hardcoded secrets — use AWS Secrets Manager or SSM Parameter Store
- No inline IAM policies — use
aws_iam_policyresources with JSON documents - Least privilege — IAM policies grant minimum required permissions
- No public S3 buckets unless explicitly justified and documented
- Encryption at rest on all storage: S3, RDS, EBS, EFS
- Security groups: deny all by default, allow specific CIDRs and ports only
- No
0.0.0.0/0ingress on SSH/RDP — use bastion or SSM Session Manager - All RDS instances:
publicly_accessible = false, inside private subnets
- Data lookups: use
datasources for AMIs, AZs, account ID — do not hardcode - Conditional resources:
count = var.enable_feature ? 1 : 0 - Multiple instances:
for_eachwith maps for named resources - Dependencies: use
depends_ononly when implicit deps are insufficient - Outputs: expose only what consuming modules need (IDs, ARNs, endpoints)
- Do not
applywithout reviewingplanoutput first - Do not modify
.terraform.lock.hclmanually — runterraform init -upgrade - Do not store
*.tfvarswith secrets in git — use env vars or secrets manager - Do not edit state files manually — use
terraform state mv/rmcommands - Do not create resources outside of Terraform for managed infrastructure
- Do not run
terraform destroyon staging/prod without explicit approval