Terraform AWS Provider Patterns Prompt
Common Terraform AWS patterns — VPC + subnets, EKS, RDS, ALB, S3, IAM roles, naming conventions.
- Target user
- Terraform engineers building AWS infrastructure
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT
The prompt
You are a senior cloud engineer who has built AWS infrastructure with Terraform — VPC, EKS, RDS, ALB, IAM — at scale. I will provide: - The AWS resource pattern - Current code - Goal (write / refactor / debug) Your job: 1. **For VPC + subnets**: - Public + private subnets per AZ - NAT Gateway for private egress - Internet Gateway for public - Route tables wired 2. **For EKS cluster**: - Cluster + node groups - IAM for cluster, node - Security groups - Add-ons (VPC CNI, CoreDNS, kube-proxy) 3. **For RDS**: - Subnet group - Parameter group - Multi-AZ for prod - Encryption - Backup 4. **For ALB**: - Listener + listener rules - Target group with health check - WAF integration 5. **For S3**: - Versioning - Encryption (SSE-S3 or SSE-KMS) - Lifecycle rules - Block public access 6. **For IAM**: - Roles with assume role policies - Inline vs managed policies - Trust policies 7. **For naming**: - Consistent prefix per env / team - Reflects ownership 8. **For tagging**: - default_tags on provider - Override per resource Mark DESTRUCTIVE: destroying RDS without snapshot (data loss), removing IAM trust without alternative, S3 bucket policy too permissive. --- Pattern: [DESCRIBE] Current code: [PASTE] Goal: [DESCRIBE]
Why this prompt works
AWS patterns are common. This prompt walks them.
How to use it
- Use community modules where appropriate.
- Follow consistent naming.
- Tag everything.
- Encrypt by default.
Patterns
VPC with public + private subnets
locals {
vpc_cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
resource "aws_vpc" "main" {
cidr_block = local.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.environment}-vpc"
}
}
resource "aws_subnet" "public" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
availability_zone = local.azs[count.index]
cidr_block = cidrsubnet(local.vpc_cidr, 8, count.index)
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-${local.azs[count.index]}"
Tier = "public"
}
}
resource "aws_subnet" "private" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
availability_zone = local.azs[count.index]
cidr_block = cidrsubnet(local.vpc_cidr, 8, count.index + 100)
tags = {
Name = "${var.environment}-private-${local.azs[count.index]}"
Tier = "private"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
}
}
resource "aws_eip" "nat" {
count = length(local.azs)
domain = "vpc"
}
resource "aws_nat_gateway" "main" {
count = length(local.azs)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
}
resource "aws_route_table_association" "public" {
count = length(local.azs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
}
resource "aws_route_table_association" "private" {
count = length(local.azs)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
RDS PostgreSQL
resource "aws_db_subnet_group" "main" {
name = "${var.environment}-rds-subnets"
subnet_ids = aws_subnet.private[*].id
}
resource "aws_db_parameter_group" "postgres" {
name = "${var.environment}-postgres-params"
family = "postgres16"
parameter {
name = "log_statement"
value = "ddl"
}
parameter {
name = "log_min_duration_statement"
value = "1000" # log queries > 1s
}
}
resource "aws_db_instance" "main" {
identifier = "${var.environment}-postgres"
engine = "postgres"
engine_version = "16.3"
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
storage_type = "gp3"
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
db_name = "myapp"
username = "admin"
password = random_password.db.result
db_subnet_group_name = aws_db_subnet_group.main.name
parameter_group_name = aws_db_parameter_group.postgres.name
vpc_security_group_ids = [aws_security_group.rds.id]
multi_az = var.environment == "prod"
backup_retention_period = var.environment == "prod" ? 35 : 7
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
deletion_protection = var.environment == "prod"
skip_final_snapshot = var.environment != "prod"
final_snapshot_identifier = var.environment == "prod" ? "${var.environment}-postgres-final-${replace(timestamp(), ":", "-")}" : null
performance_insights_enabled = true
enabled_cloudwatch_logs_exports = ["postgresql"]
lifecycle {
ignore_changes = [
final_snapshot_identifier,
password,
]
}
}
resource "aws_security_group" "rds" {
name = "${var.environment}-rds"
description = "RDS access from app tier"
vpc_id = aws_vpc.main.id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
}
IAM role for service
resource "aws_iam_role" "app" {
name = "${var.environment}-app"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy" "app" {
name = "${var.environment}-app-policy"
role = aws_iam_role.app.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["s3:GetObject", "s3:PutObject"]
Resource = "${aws_s3_bucket.app_data.arn}/*"
},
{
Effect = "Allow"
Action = ["secretsmanager:GetSecretValue"]
Resource = aws_secretsmanager_secret.db.arn
}
]
})
}
resource "aws_iam_instance_profile" "app" {
name = "${var.environment}-app"
role = aws_iam_role.app.name
}
S3 bucket secure default
resource "aws_s3_bucket" "data" {
bucket = "${var.environment}-data-${random_id.suffix.hex}"
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
id = "archive-old"
status = "Enabled"
transition {
days = 90
storage_class = "GLACIER"
}
noncurrent_version_expiration {
noncurrent_days = 365
}
}
}
Common findings this catches
- RDS without deletion_protection in prod → enable.
- S3 without encryption → SSE-KMS.
- VPC without flow logs → enable for audit.
- IAM trust too broad → scope to specific.
- No multi-AZ in prod RDS → enable.
- Manual tags only → default_tags on provider.
- VPC CIDR too small → /16 typical.
When to escalate
- Multi-account architecture — strategic.
- AWS quota issues — finance.
- Network design across VPCs — coordinate.
Related prompts
-
Kubernetes Cluster Upgrade Pre-Flight Planning Prompt
Pre-upgrade safety review of a Kubernetes cluster going N → N+1 (or N+2 skip) — deprecated APIs, removed features, control-plane & node ordering, workload compatibility.
-
Terraform Provider Configuration & Aliases Prompt
Configure Terraform providers — version constraints, aliases for multi-region/multi-account, required_providers.
-
Terraform Secrets & Sensitive Variables Prompt
Manage secrets in Terraform — sensitive flag, ephemeral resources, external secret managers, plan/state masking.