FinOpsAWS

AWS Cost Optimization: 10 Terraform Patterns to Cut Your Bill by 40%

NT

Naveen Teja

3/2/2026

AWS Cost Optimization: 10 Terraform Patterns to Cut Your Bill by 40%

AWS bills have a way of growing silently. Unused Elastic IPs, orphaned EBS volumes, oversized RDS instances running 24/7 for a development database, NAT Gateways with minimal traffic — these small inefficiencies compound into thousands of dollars of monthly waste. Cost optimization is consistently ranked as the top cloud challenge by engineering teams.

The foundational principle is matching resource cost to actual usage. This means using Spot Instances for fault-tolerant batch workloads (up to 90% cheaper than On-Demand), Savings Plans for predictable Lambda and compute spend, and auto-scaling policies that scale in aggressively during off-peak hours. For RDS development instances, a simple Lambda-based scheduler that stops the database at night and restarts it in the morning can cut those costs by 65%.

Four high-impact Terraform patterns stand out: First, enable S3 Intelligent-Tiering on any bucket storing objects accessed unpredictably. Second, set DynamoDB tables to PAY_PER_REQUEST for spiky workloads. Third, use NAT Instance (t4g.nano) instead of NAT Gateway for non-production VPCs. Fourth, configure CloudWatch alarms on EstimatedCharges to alert before bills spike. The snippet below implements an automatic RDS scheduler using EventBridge and Lambda to stop non-production databases outside business hours.

cost-optimization.tf
# Stop RDS outside business hours using EventBridge scheduler
resource "aws_lambda_function" "rds_scheduler" {
  filename      = "rds_scheduler.zip"
  function_name = "rds-cost-scheduler"
  role          = aws_iam_role.rds_scheduler.arn
  handler       = "index.handler"
  runtime       = "python3.12"
}

# Stop at 8 PM weekdays
resource "aws_cloudwatch_event_rule" "stop_rds" {
  name                = "stop-dev-rds"
  schedule_expression = "cron(0 20 ? * MON-FRI *)"
}

resource "aws_cloudwatch_event_target" "stop" {
  rule  = aws_cloudwatch_event_rule.stop_rds.name
  arn   = aws_lambda_function.rds_scheduler.arn
  input = jsonencode({ action = "stop", identifier = "dev-aurora-cluster" })
}

# Start at 7 AM weekdays
resource "aws_cloudwatch_event_rule" "start_rds" {
  name                = "start-dev-rds"
  schedule_expression = "cron(0 7 ? * MON-FRI *)"
}

resource "aws_cloudwatch_event_target" "start" {
  rule  = aws_cloudwatch_event_rule.start_rds.name
  arn   = aws_lambda_function.rds_scheduler.arn
  input = jsonencode({ action = "start", identifier = "dev-aurora-cluster" })
}

# Billing alarm
resource "aws_cloudwatch_metric_alarm" "billing_alert" {
  alarm_name          = "monthly-billing-alert"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 1
  metric_name         = "EstimatedCharges"
  namespace           = "AWS/Billing"
  period              = 86400
  statistic           = "Maximum"
  threshold           = 200
  alarm_actions       = [aws_sns_topic.billing_alerts.arn]

  dimensions = {
    Currency = "USD"
  }
}