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โ
| Variable | Used by | Description |
|---|---|---|
name | All | Base resource name |
environment | All | prod, staging, dev |
label_order | All | ["name","environment"] default |
managedby | All | Tag value |
enable / enabled | All | Toggle resource creation |
vpc_id | Subnet, SG, EKS, Aurora, Elasticache | Target VPC |
subnet_ids | EKS, Aurora, Elasticache | Subnet placement |
availability_zones | Subnet | Which AZs to create subnets in |
cidr_block | VPC | VPC CIDR |
ipv4_public_cidrs | Subnet | CIDRs for public subnets |
ipv4_private_cidrs | Subnet | CIDRs for private subnets |
nat_gateway_enabled | Subnet | Create NAT Gateways |
kubernetes_version | EKS | EKS Kubernetes version |
managed_node_group | EKS | Node group definitions map |
oidc_provider_enabled | EKS | Enable IRSA OIDC provider |
Reference: terraform-aws-labels โ ยท Full module list โ