AWS infrastructure patterns and best practices for Terraform. Provides VPC, IAM, S3, and security group scaffolds. Use when developing AWS infrastructure.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Terraform patterns for AWS infrastructure development.
ALWAYS use doc-researcher or Terraform MCP to verify current provider schemas.
Use {project}-{environment}-{resource} pattern consistently:
locals {
name_prefix = "${var.project}-${var.environment}"
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${local.name_prefix}-vpc"
cidr = var.vpc_cidr # e.g., "10.0.0.0/16"
azs = data.aws_availability_zones.available.names
private_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 4, k)]
public_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 48)]
intra_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 52)]
enable_nat_gateway = true
single_nat_gateway = var.environment != "prod"
enable_dns_hostnames = true
enable_dns_support = true
# EKS requirements
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = 1
}
tags = var.tags
}
data "aws_availability_zones" "available" {
state = "available"
}
locals {
azs = slice(data.aws_availability_zones.available.names, 0, 3)
}
module "vpc_endpoints" {
source = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints"
version = "~> 5.0"
vpc_id = module.vpc.vpc_id
endpoints = {
s3 = {
service = "s3"
service_type = "Gateway"
route_table_ids = module.vpc.private_route_table_ids
tags = { Name = "${local.name_prefix}-s3-endpoint" }
}
ecr_api = {
service = "ecr.api"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.vpc_endpoints.id]
}
ecr_dkr = {
service = "ecr.dkr"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.vpc_endpoints.id]
}
sts = {
service = "sts"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.vpc_endpoints.id]
}
}
tags = var.tags
}
resource "aws_security_group" "vpc_endpoints" {
name_prefix = "${local.name_prefix}-vpc-endpoints-"
vpc_id = module.vpc.vpc_id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [module.vpc.vpc_cidr_block]
}
tags = merge(var.tags, { Name = "${local.name_prefix}-vpc-endpoints" })
}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_role" "service" {
name = "${local.name_prefix}-service-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
tags = var.tags
}
resource "aws_iam_instance_profile" "service" {
name = "${local.name_prefix}-service-profile"
role = aws_iam_role.service.name
}
data "aws_iam_policy_document" "permissions" {
statement {
sid = "S3ReadAccess"
effect = "Allow"
actions = [
"s3:GetObject",
"s3:ListBucket"
]
resources = [
aws_s3_bucket.data.arn,
"${aws_s3_bucket.data.arn}/*"
]
}
statement {
sid = "SecretsManagerAccess"
effect = "Allow"
actions = [
"secretsmanager:GetSecretValue"
]
resources = [
"arn:aws:secretsmanager:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:secret:${var.project}/*"
]
}
}
resource "aws_iam_role_policy" "service" {
name = "${local.name_prefix}-service-policy"
role = aws_iam_role.service.id
policy = data.aws_iam_policy_document.permissions.json
}
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
locals {
account_id = data.aws_caller_identity.current.account_id
region = data.aws_region.current.name
}
resource "aws_s3_bucket" "data" {
bucket = "${local.name_prefix}-data-${local.account_id}"
tags = var.tags
}
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 = "transition-to-ia"
status = "Enabled"
transition {
days = 30
storage_class = "STANDARD_IA"
}
transition {
days = 90
storage_class = "GLACIER"
}
noncurrent_version_expiration {
noncurrent_days = 30
}
}
}
# ALB Security Group - Public facing
resource "aws_security_group" "alb" {
name_prefix = "${local.name_prefix}-alb-"
vpc_id = module.vpc.vpc_id
ingress {
description = "HTTPS from internet"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
tags = merge(var.tags, { Name = "${local.name_prefix}-alb" })
}
# Application Security Group - Only from ALB
resource "aws_security_group" "app" {
name_prefix = "${local.name_prefix}-app-"
vpc_id = module.vpc.vpc_id
ingress {
description = "From ALB"
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
tags = merge(var.tags, { Name = "${local.name_prefix}-app" })
}
# Database Security Group - Only from App
resource "aws_security_group" "db" {
name_prefix = "${local.name_prefix}-db-"
vpc_id = module.vpc.vpc_id
ingress {
description = "PostgreSQL from app"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
lifecycle {
create_before_destroy = true
}
tags = merge(var.tags, { Name = "${local.name_prefix}-db" })
}
resource "aws_kms_key" "main" {
description = "KMS key for ${local.name_prefix}"
deletion_window_in_days = 7
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "Enable IAM User Permissions"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${local.account_id}:root"
}
Action = "kms:*"
Resource = "*"
}
]
})
tags = var.tags
}
resource "aws_kms_alias" "main" {
name = "alias/${local.name_prefix}-main"
target_key_id = aws_kms_key.main.key_id
}
variable "project" {
description = "Project name for resource naming and tagging"
type = string
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "additional_tags" {
description = "Additional tags to apply to all resources"
type = map(string)
default = {}
}
locals {
default_tags = {
Project = var.project
Environment = var.environment
Terraform = "true"
ManagedBy = "terraform"
}
tags = merge(local.default_tags, var.additional_tags)
}
# Current AWS context
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
data "aws_partition" "current" {}
# Availability zones
data "aws_availability_zones" "available" {
state = "available"
}
# Latest Amazon Linux 2 AMI
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# SSM Parameter for EKS AMI
data "aws_ssm_parameter" "eks_ami" {
name = "/aws/service/eks/optimized-ami/${var.cluster_version}/amazon-linux-2/recommended/image_id"
}