Skip to content
CloudOps
All prompts
AI for Terraform Difficulty: Advanced ClaudeChatGPT

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

  1. Use community modules where appropriate.
  2. Follow consistent naming.
  3. Tag everything.
  4. 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

Newsletter

Get weekly AI workflows for DevOps engineers

Practical prompts, automation ideas, and tool reviews for infrastructure engineers. One email per week. No spam.