· AWS · 3 min read
AWS Cost Optimization — Real Strategies That Actually Work
AWS bills can spiral fast. Here are the strategies I use in production to cut cloud costs without sacrificing performance or reliability.
Why AWS Bills Surprise Everyone
AWS is pay-as-you-go — which sounds great until you get your first serious bill. The problem isn’t that AWS is expensive, it’s that unused, over-provisioned, or forgotten resources silently accumulate cost. I’ve audited dozens of AWS accounts and the same patterns appear every time.
1. Right-Size Your EC2 and RDS Instances
The most common waste I find: instances sized for peak load that never actually arrive. Use AWS Cost Explorer’s rightsizing recommendations as a starting point, then validate with real metrics from CloudWatch or Grafana.
# Check average CPU utilization over 2 weeks
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-14T00:00:00Z \
--period 86400 \
--statistics AverageIf average CPU sits below 20%, you’re likely over-provisioned.
2. Use Spot Instances for Non-Critical Workloads
Spot instances offer up to 90% discount over On-Demand. For Kubernetes, I configure Karpenter to prefer Spot for worker nodes with automatic fallback:
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m5.large", "m5.xlarge", "m4.large", "m4.xlarge"]
ttlSecondsAfterEmpty: 30Karpenter consolidates nodes automatically — another big cost win.
3. S3 Lifecycle Policies
Data that nobody accesses shouldn’t sit in S3 Standard. Move it automatically:
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
bucket = aws_s3_bucket.logs.id
rule {
id = "transition-old-logs"
status = "Enabled"
transition {
days = 30
storage_class = "STANDARD_IA"
}
transition {
days = 90
storage_class = "GLACIER"
}
expiration {
days = 365
}
}
}This alone can cut S3 costs by 60–80% for log-heavy workloads.
4. CloudFront in Front of Everything
Serving assets directly from S3 or your origin costs more than it should. CloudFront caches at the edge, reducing origin requests dramatically. For a typical web app with static assets:
resource "aws_cloudfront_distribution" "app" {
origin {
domain_name = aws_s3_bucket.assets.bucket_regional_domain_name
origin_id = "S3-assets"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
}
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-assets"
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
}
enabled = true
}5. Reserved Instances and Savings Plans
For predictable workloads (production databases, base Kubernetes nodes), commit to 1-year Reserved Instances or Compute Savings Plans. Savings of 30–40% over On-Demand with zero operational change.
6. Lambda — Don’t Overlook the Free Tier
AWS Lambda has a generous free tier (1M requests/month). Offloading lightweight, event-driven tasks from always-on EC2 instances to Lambda can eliminate those instances entirely.
Quick Wins Checklist
| Action | Typical Saving |
|---|---|
| Delete unattached EBS volumes | 100% of that volume cost |
| Remove unused Elastic IPs | $3.65/month each |
| Rightsize over-provisioned RDS | 30–50% |
| Enable S3 Intelligent-Tiering | 20–40% on S3 |
| Spot instances for EKS nodes | Up to 70% on compute |
| CloudFront caching | 30–60% on data transfer |
Conclusion
AWS cost optimization isn’t a one-time task — it’s a habit. Set up monthly Cost Explorer reviews, tag everything (seriously, tag everything), and build cost awareness into your infrastructure-as-code from day one. The savings compound over time.
---