Skip to main content

AWS Recipes


Recipe 1 โ€” VPC + Subnet Baselineโ€‹

The networking foundation used by every other recipe. Creates a VPC, public and private subnets across three AZs, NAT Gateway, and a default security group.

# networking/main.tf

module "vpc" {
source = "git::https://github.com/clouddrove/terraform-aws-vpc.git?ref=2.0.0"

name = "main"
environment = var.environment
label_order = ["name", "environment"]

cidr_block = "10.0.0.0/16"
enable_flow_log = true
flow_log_destination_type = "cloud-watch-logs"
create_flow_log_cloudwatch_iam_role = true
}

module "subnets" {
source = "git::https://github.com/clouddrove/terraform-aws-subnet.git?ref=2.1.0"

name = "main"
environment = var.environment
label_order = ["name", "environment"]

vpc_id = module.vpc.vpc_id
igw_id = module.vpc.igw_id
availability_zones = ["${var.region}a", "${var.region}b", "${var.region}c"]
type = "public-private"

ipv4_public_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
ipv4_private_cidrs = ["10.0.10.0/24", "10.0.11.0/24", "10.0.12.0/24"]

nat_gateway_enabled = true
single_nat_gateway = var.environment != "prod" # single NAT in non-prod to save cost
}

module "sg_alb" {
source = "git::https://github.com/clouddrove/terraform-aws-security-group.git?ref=2.0.0"

name = "alb"
environment = var.environment
vpc_id = module.vpc.vpc_id

new_sg_ingress_rules_with_cidr_blocks = {
https = {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS from internet"
}
http = {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP (redirect to HTTPS)"
}
}
new_sg_egress_rules_with_cidr_blocks = {
all = {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound"
}
}
}

module "sg_app" {
source = "git::https://github.com/clouddrove/terraform-aws-security-group.git?ref=2.0.0"

name = "app"
environment = var.environment
vpc_id = module.vpc.vpc_id

new_sg_ingress_rules_with_source_sg_id = {
from_alb = {
from_port = 8080
to_port = 8080
protocol = "tcp"
source_security_group_id = module.sg_alb.security_group_id
description = "Traffic from ALB"
}
}
}

Outputs used by other recipes:

# networking/outputs.tf
output "vpc_id" { value = module.vpc.vpc_id }
output "public_subnet_ids" { value = module.subnets.public_subnet_id }
output "private_subnet_ids" { value = module.subnets.private_subnet_id }
output "sg_alb_id" { value = module.sg_alb.security_group_id }
output "sg_app_id" { value = module.sg_app.security_group_id }

Recipe 2 โ€” EKS Clusterโ€‹

Production EKS cluster with managed node groups, OIDC/IRSA, and encryption. Builds on the networking recipe above.

# eks/main.tf
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "my-org-terraform-state"
key = "${var.environment}/networking/terraform.tfstate"
region = var.region
}
}

module "eks" {
source = "git::https://github.com/clouddrove/terraform-aws-eks.git?ref=2.1.0"

name = "cluster"
environment = var.environment
label_order = ["name", "environment"]

vpc_id = data.terraform_remote_state.networking.outputs.vpc_id
subnet_ids = data.terraform_remote_state.networking.outputs.private_subnet_ids

kubernetes_version = "1.31"
oidc_provider_enabled = true
endpoint_private_access = true
endpoint_public_access = true
public_access_cidrs = ["0.0.0.0/0"] # restrict to your office IPs in prod

# Control plane logging
enabled_cluster_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"]
cluster_log_retention_period = 30

# Secrets encryption at rest
cluster_encryption_config_enabled = true
cluster_encryption_config_resources = ["secrets"]

# Managed node group
managed_node_group = {
general = {
node_group_name = "general"
instance_types = ["t3.medium"]
min_size = 2
max_size = 10
desired_size = 3
disk_size = 50
capacity_type = "ON_DEMAND"

labels = {
role = "general"
}
}
spot = {
node_group_name = "spot"
instance_types = ["t3.large", "t3a.large", "m5.large"]
min_size = 0
max_size = 20
desired_size = 0
capacity_type = "SPOT"

labels = {
role = "spot"
}
taints = {
spot = {
key = "spot"
value = "true"
effect = "NO_SCHEDULE"
}
}
}
}

# Allow CI/CD role to access cluster
map_additional_iam_roles = [
{
rolearn = "arn:aws:iam::${var.account_id}:role/CICDRole"
username = "cicd"
groups = ["system:masters"]
}
]

tags = {
"karpenter.sh/discovery" = "cluster-${var.environment}"
}
}

# Install add-ons after cluster is ready
module "eks_addons" {
source = "git::https://github.com/clouddrove/terraform-aws-eks-addons.git?ref=0.0.7"

eks_cluster_name = module.eks.cluster_name
data_plane_wait_arn = module.eks.node_security_group_arn

metrics_server = true
cluster_autoscaler = true
aws_load_balancer_controller = true
external_dns = true
certification_manager = true
external_secrets = true
}

Key outputs:

output "cluster_endpoint"                   { value = module.eks.cluster_endpoint }
output "cluster_certificate_authority_data" { value = module.eks.cluster_certificate_authority_data }
output "oidc_provider_arn" { value = module.eks.oidc_provider_arn }
output "cluster_name" { value = module.eks.cluster_name }

Recipe 3 โ€” Serverless APIโ€‹

API Gateway + Lambda + DynamoDB. No servers, scales to zero.

# serverless/main.tf

module "kms" {
source = "git::https://github.com/clouddrove/terraform-aws-kms.git?ref=1.3.2"

name = "serverless"
environment = var.environment

description = "KMS key for serverless API encryption"
enable_key_rotation = true
deletion_window_in_days = 10
}

module "dynamodb" {
source = "git::https://github.com/clouddrove/terraform-aws-dynamodb.git?ref=1.0.1"

name = "users"
environment = var.environment

hash_key = "userId"
range_key = "createdAt"

attributes = [
{ name = "userId", type = "S" },
{ name = "createdAt", type = "S" }
]

server_side_encryption_enabled = true
server_side_encryption_kms_key_arn = module.kms.key_arn

point_in_time_recovery_enabled = true
}

module "lambda" {
source = "git::https://github.com/clouddrove/terraform-aws-lambda.git?ref=1.3.4"

name = "api-handler"
environment = var.environment

filename = "${path.module}/dist/handler.zip"
handler = "index.handler"
runtime = "nodejs20.x"
memory_size = 512
timeout = 30

environment_variables = {
DYNAMODB_TABLE = module.dynamodb.table_name
REGION = var.region
}

# IAM permissions for the Lambda
additional_iam_policy_arns = [module.dynamodb_policy.arn]
}

module "api_gateway" {
source = "git::https://github.com/clouddrove/terraform-aws-api-gateway.git?ref=1.4.1"

name = "api"
environment = var.environment

protocol_type = "HTTP"

# Route: POST /users โ†’ Lambda
integration_uri = module.lambda.arn
integration_type = "AWS_PROXY"
integration_method = "POST"

route_key = "POST /users"

# CORS
cors_configuration = {
allow_origins = ["https://app.example.com"]
allow_methods = ["GET", "POST", "PUT", "DELETE"]
allow_headers = ["Content-Type", "Authorization"]
max_age = 300
}
}

Recipe 4 โ€” Three-Tier Web Appโ€‹

ALB + Aurora PostgreSQL + ElastiCache Redis โ€” the classic production stack for a containerised web application.

# app-infrastructure/main.tf
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "my-org-terraform-state"
key = "${var.environment}/networking/terraform.tfstate"
region = var.region
}
}

locals {
vpc_id = data.terraform_remote_state.networking.outputs.vpc_id
private_subnets = data.terraform_remote_state.networking.outputs.private_subnet_ids
public_subnets = data.terraform_remote_state.networking.outputs.public_subnet_ids
}

# โ”€โ”€ TLS Certificate โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

module "acm" {
source = "git::https://github.com/clouddrove/terraform-aws-acm.git?ref=1.4.1"

name = "app"
environment = var.environment

domain_name = "app.${var.domain}"
validation_method = "DNS"
}

# โ”€โ”€ Load Balancer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

module "alb" {
source = "git::https://github.com/clouddrove/terraform-aws-alb.git?ref=1.4.1"

name = "app"
environment = var.environment

vpc_id = local.vpc_id
subnet_ids = local.public_subnets

https_enabled = true
http_redirect = true
certificate_arn = module.acm.arn
target_type = "ip"
target_group_port = 8080
health_check_path = "/health"
load_balancing_algorithm = "round_robin"
}

# โ”€โ”€ Database โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

module "sg_db" {
source = "git::https://github.com/clouddrove/terraform-aws-security-group.git?ref=2.0.0"

name = "db"
environment = var.environment
vpc_id = local.vpc_id

new_sg_ingress_rules_with_source_sg_id = {
from_app = {
from_port = 5432
to_port = 5432
protocol = "tcp"
source_security_group_id = data.terraform_remote_state.networking.outputs.sg_app_id
description = "PostgreSQL from app tier"
}
}
}

module "aurora" {
source = "git::https://github.com/clouddrove/terraform-aws-aurora.git?ref=1.3.0"

name = "app-db"
environment = var.environment

engine = "aurora-postgresql"
engine_version = "15.4"
instance_type = "db.t4g.medium"

vpc_id = local.vpc_id
subnet_ids = local.private_subnets
sg_ids = [module.sg_db.security_group_id]

storage_encrypted = true
deletion_protection = true
skip_final_snapshot = var.environment != "prod"

backup_retention_period = 7
preferred_backup_window = "03:00-04:00"

database_name = "appdb"
master_username = "appuser"

# Password pulled from Secrets Manager by the app at runtime
manage_master_user_password = true
}

# โ”€โ”€ Cache โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

module "sg_cache" {
source = "git::https://github.com/clouddrove/terraform-aws-security-group.git?ref=2.0.0"

name = "cache"
environment = var.environment
vpc_id = local.vpc_id

new_sg_ingress_rules_with_source_sg_id = {
from_app = {
from_port = 6379
to_port = 6379
protocol = "tcp"
source_security_group_id = data.terraform_remote_state.networking.outputs.sg_app_id
description = "Redis from app tier"
}
}
}

module "elasticache" {
source = "git::https://github.com/clouddrove/terraform-aws-elasticache.git?ref=1.3.2"

name = "app-cache"
environment = var.environment

engine = "redis"
engine_version = "7.0"
node_type = "cache.t4g.small"
num_cache_nodes = 1
cluster_mode_enabled = false

subnet_ids = local.private_subnets
security_group_ids = [module.sg_cache.security_group_id]

at_rest_encryption_enabled = true
transit_encryption_enabled = true

automatic_failover_enabled = var.environment == "prod"
}

# โ”€โ”€ Monitoring โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

module "alarms" {
source = "git::https://github.com/clouddrove/terraform-aws-cloudwatch-alarms.git?ref=1.3.3"

name = "app"
environment = var.environment

alarm_actions = [module.sns_alerts.arn]

alarms = {
high_cpu = {
alarm_name = "app-high-cpu"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/RDS"
period = 300
statistic = "Average"
threshold = 80
}
db_connections = {
alarm_name = "app-db-connections"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
metric_name = "DatabaseConnections"
namespace = "AWS/RDS"
period = 60
statistic = "Average"
threshold = 100
}
}
}

module "sns_alerts" {
source = "git::https://github.com/clouddrove/terraform-aws-sns.git?ref=1.3.1"

name = "alerts"
environment = var.environment

subscribers = {
oncall = {
protocol = "email"
endpoint = var.alert_email
}
}
}

Variable Referenceโ€‹

VariableUsed byDescription
nameAllBase resource name
environmentAllprod, staging, dev
label_orderAll["name","environment"] default
managedbyAllTag value
enable / enabledAllToggle resource creation
vpc_idSubnet, SG, EKS, Aurora, ElasticacheTarget VPC
subnet_idsEKS, Aurora, ElasticacheSubnet placement
availability_zonesSubnetWhich AZs to create subnets in
cidr_blockVPCVPC CIDR
ipv4_public_cidrsSubnetCIDRs for public subnets
ipv4_private_cidrsSubnetCIDRs for private subnets
nat_gateway_enabledSubnetCreate NAT Gateways
kubernetes_versionEKSEKS Kubernetes version
managed_node_groupEKSNode group definitions map
oidc_provider_enabledEKSEnable IRSA OIDC provider

Reference: terraform-aws-labels โ†’ ยท Full module list โ†’