How to reduce your AWS bill by 30-60% without sacrificing performance or reliability.
Introduction
Cloud bills grow silently. A team provisions a large RDS instance “just in case,” dev environments run 24/7 when developers work 8 hours, and nobody notices the NAT Gateway quietly charging $0.045/GB on every outbound byte.
FinOps (Financial Operations) isn’t about cutting corners — it’s about spending wisely. The goal: every pound spent on infrastructure delivers business value. Idle resources, over-provisioned instances, and unused storage are waste — not safety.
In this post, I’ll walk through practical cost optimization strategies across Compute, Networking, Databases, and Storage — with real numbers and tools you can implement today.
The FinOps Mindset
Before diving into tactics, here’s the principle:
“Cost optimization is a continuous practice, not a one-time project. And the number one rule: never compromise security or reliability to save money.“
The approach:
- Identify — where is the money going? (visibility)
- Optimize — right-size, schedule, choose the right pricing model
- Automate — scale down when not needed, eliminate waste automatically
- Govern — prevent runaway spending before it happens
Compute Optimization (EKS / ECS / EC2)
Compute is typically 60-70% of your AWS bill. This is where the biggest savings live.
Right-Size Your Instances
Most EC2 instances and Kubernetes pods are over-provisioned. Teams provision for peak load and then never revisit.
How to identify waste:
- AWS Compute Optimizer — free, analyses 14 days of CloudWatch metrics, recommends smaller instance types
- Datadog resource metrics — check actual CPU/memory usage vs requested/limits
kubectl top pods— see real utilisation vs what’s allocated
Example savings:
Before: m5.xlarge (4 vCPU, 16 GB) running at 15% CPU, 30% memory
After: m5.large (2 vCPU, 8 GB) running at 30% CPU, 60% memory
Savings: 50% on that instance (~$70/month per instance)
Use Spot Instances for Non-Critical Workloads
| Workload | Spot Suitable? | Savings |
|---|---|---|
| CI/CD runners (GitLab) | Yes — jobs are short, retryable | 60-90% |
| Dev/test environments | Yes — interruption acceptable | 60-90% |
| Batch processing | Yes — checkpointable | 60-90% |
| Production APIs | No — use Savings Plans instead | — |
| Stateful databases | No — data integrity risk | — |
Best practice for Spot:
- Request 6+ instance types (don’t depend on one type)
- Use capacity-optimized allocation strategy
- Fall back to On-Demand if no Spot available
- Handle the 2-minute interruption warning gracefully
Savings Plans / Reserved Instances for Stable Workloads
| Commitment | Discount | Best For |
|---|---|---|
| No commitment (On-Demand) | 0% | Unpredictable, short-term |
| 1-year Savings Plan (no upfront) | 20-30% | Production workloads you’ll run for a year |
| 1-year Savings Plan (all upfront) | 30-40% | Stable production, budget available |
| 3-year Savings Plan (all upfront) | 50-60% | Long-term committed infrastructure |
Tip: Start with Compute Savings Plans (flexible across instance families) rather than EC2 Reserved Instances (locked to specific type).
Karpenter for EKS (Automatic Node Right-Sizing)
“Karpenter is the single biggest EKS cost optimization tool. It replaces Cluster Autoscaler and makes better decisions.”
What Karpenter does:
- Picks the cheapest instance type that fits your pod requirements
- Consolidates pods onto fewer nodes (removes empty/underused nodes)
- Supports Spot + On-Demand mixed strategies
- Launches new nodes in 30 seconds (vs 4-5 minutes for Cluster Autoscaler)
Real impact: At our organisation, Karpenter reduced EKS compute costs by ~30% by consolidating workloads and choosing optimal instance types automatically.
Schedule Non-Production Environments
Dev and staging environments running 24/7 waste 65% of their cost (only used ~50 hours/week out of 168).
Implementation with kube-downscaler:
# Scale to zero pods outside business hours
metadata:
annotations:
downscaler/downtime: "Mon-Fri 19:00-07:00 Europe/London,Sat-Sun 00:00-24:00 Europe/London"
Savings calculation:
10 proxy services × 2 pods each × m6g.large ($0.077/hr)
Running 24/7: $0.077 × 20 pods × 730 hours = $1,124/month
Running 50hrs: $0.077 × 20 pods × 217 hours = $334/month
Savings: $790/month (70% reduction)
Networking Optimization
Networking costs are the silent killer — they don’t appear as a clear line item and grow with traffic.
VPC Endpoints (Biggest Quick Win)
| Traffic Type | Without Endpoint | With Endpoint | Savings |
|---|---|---|---|
| S3 access | NAT GW: $0.045/GB processing + data transfer | Gateway endpoint: FREE | 100% |
| ECR image pulls | NAT GW: $0.045/GB | Interface endpoint: $7.20/month flat | Huge for image-heavy clusters |
| Secrets Manager | NAT GW per call | Interface endpoint: flat fee | Significant for frequent secret fetches |
Rule: If a service talks to S3 or DynamoDB, ALWAYS use a Gateway endpoint. It’s free and eliminates NAT charges for that traffic.
NAT Gateway Per AZ
Problem: A single NAT Gateway in one AZ means ALL other AZs send traffic cross-AZ ($0.01/GB each way) just to reach the NAT.
Fix: Deploy one NAT Gateway per AZ with AZ-specific route tables.
Cost of 3 NAT Gateways: 3 × $0.045/hr = $97/month
Cross-AZ savings for a busy cluster: $200-500/month
Net savings: $100-400/month
CloudFront Caching
- Cache static assets at the edge — reduces origin requests (and data transfer)
- Set appropriate TTLs (CSS/JS: 1 year with cache-busting, API: seconds/minutes)
- Use Origin Shield to reduce origin load further
Reduce Cross-AZ Traffic
- Enable Kubernetes Topology Aware Routing (pods prefer same-AZ endpoints)
- Keep chatty service pairs in the same AZ using pod affinity
- Use
GIT_DEPTH: 1in CI/CD to reduce clone data transfer
Database Optimization (RDS / DynamoDB / Aurora)
Right-Size Your Database
“Most production databases are over-provisioned. Check CloudWatch: if CPU averages 20% and max is 40%, you can safely downsize.”
Before: db.r6g.xlarge (4 vCPU, 32 GB) — $0.48/hr = $350/month
After: db.r6g.large (2 vCPU, 16 GB) — $0.24/hr = $175/month
Savings: $175/month per instance
Aurora Serverless v2 (Pay Per Use)
For databases with variable traffic — busy during the day, idle at night — Aurora Serverless scales down to 0.5 ACU when idle.
Traditional RDS (always on): db.r6g.large × 730 hours = $175/month
Aurora Serverless (variable): 0.5 ACU × 12 hrs/night + 4 ACU × 12 hrs/day = ~$95/month
Savings: ~45%
Stop Dev/Test Databases Outside Business Hours
# Lambda + EventBridge: stop at 7pm, start at 8am
aws rds stop-db-instance --db-instance-identifier dev-booking-db
# RDS auto-starts after 7 days — use a Lambda to re-stop it
Savings: 60% for databases only used during business hours.
DynamoDB: On-Demand vs Provisioned
| Traffic Pattern | Best Mode | Why |
|---|---|---|
| Unpredictable spikes | On-Demand | Pay per request, no capacity planning |
| Steady, predictable | Provisioned + Auto-scaling | 5-7x cheaper than On-Demand at steady load |
| Occasional (< 1 req/sec) | On-Demand | Provisioned minimum is wasteful for low traffic |
Use Read Replicas Instead of Scaling Up
Instead of doubling your primary DB size for read-heavy workloads, add a read replica (50% the cost of scaling the primary).
Storage Optimization (S3 / EBS / EFS)
S3 Lifecycle Policies (Set and Forget)
{
"Rules": [
{
"ID": "OptimizeLogStorage",
"Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" },
{ "Days": 365, "StorageClass": "DEEP_ARCHIVE" }
],
"Expiration": { "Days": 730 }
}
]
}
Savings example:
100 TB of logs in S3 Standard: $2,300/month
With lifecycle (IA after 30d, Glacier after 90d): $350/month
Savings: 85%
S3 Intelligent-Tiering
If you don’t know the access pattern, use Intelligent-Tiering. AWS automatically moves objects between tiers based on access frequency. Small monitoring fee ($0.0025/1000 objects) but zero retrieval fees.
EBS: Switch gp2 to gp3
gp3 is 20% cheaper than gp2 AND has better baseline performance (3000 IOPS vs 100 IOPS/GB for gp2).
# Modify in-place (no downtime)
aws ec2 modify-volume --volume-id vol-xxx --volume-type gp3
Savings: 20% on all EBS volumes with zero effort.
Delete Orphaned Resources
| Resource | How to Find | Typical Waste |
|---|---|---|
| Unattached EBS volumes | aws ec2 describe-volumes --filters Name=status,Values=available |
$50-500/month |
| Old snapshots | aws ec2 describe-snapshots --owner-ids self (check age) |
$20-200/month |
| Unused Elastic IPs | aws ec2 describe-addresses (not associated) |
$3.60/month each |
| Empty load balancers | ALBs with 0 targets | $16/month each |
| Incomplete multipart uploads | S3 lifecycle rule to abort after 7 days | Variable |
General FinOps Practices (The Governance Layer)
Tagging Strategy (You Can’t Optimize What You Can’t Attribute)
tags = {
team = "starlight"
service = "availability-proxy"
environment = "production"
cost-center = "search-platform"
owner = "agnello.george"
}
Every resource tagged by team, service, and environment. Cost Explorer can then show: “Team Starlight spent £X this month, up 15% from last month.”
AWS Budgets + Anomaly Detection
resource "aws_budgets_budget" "monthly" {
name = "search-platform-monthly"
budget_type = "COST"
limit_amount = "5000"
limit_unit = "USD"
time_unit = "MONTHLY"
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
notification_type = "FORECASTED"
subscriber_email_addresses = ["devops@company.com"]
}
}
- AWS Budgets: Alert at 80% of forecast (catch spikes before month-end)
- Cost Anomaly Detection: ML-based — alerts on unexpected spend changes automatically
Weekly Cost Review Cadence
| Frequency | What | Who |
|---|---|---|
| Daily | Automated anomaly alerts | On-call engineer (acknowledge/investigate) |
| Weekly | Cost Explorer review — top 5 cost drivers | DevOps team |
| Monthly | Cost report by team/service — trends, optimizations | Engineering leads |
| Quarterly | Deep review — Savings Plans renewal, architecture changes | Platform + Finance |
Communicating Savings to Management
“Don’t say ‘let’s use Spot instances.’ Say: ‘Our CI/CD runners cost £X/month. Switching to Spot saves 70% — that’s £Y/month. Risk is low because builds are retryable. Implementation takes 2 days.’”
Present as a business case:
| Current Spend | Proposed Change | Projected Savings | Risk | Effort |
|---|---|---|---|---|
| £3,000/month CI/CD | Switch to Spot | £2,100/month (70%) | Low (retryable jobs) | 2 days |
| £1,100/month non-prod | Overnight scaling | £770/month (70%) | None (no users at night) | 1 day |
| £2,300/month S3 logs | Lifecycle policies | £1,950/month (85%) | None (old logs rarely accessed) | 1 hour |
| £6,400/month | Total | £4,820/month | 3 days work |
Real-World Results
At our organisation, applying these practices over 6 months:
| Optimization | Monthly Savings |
|---|---|
| Karpenter (EKS node right-sizing) | 30% compute reduction |
| Overnight scaling (kube-downscaler) | 70% non-prod reduction |
| S3 lifecycle policies on log buckets | 85% storage reduction |
| Spot instances for CI/CD runners | 70% runner cost reduction |
| Right-sizing 5 production services | 20-40% per service |
| VPC endpoints for S3/ECR | Eliminated NAT processing charges |
Key Takeaways
- Start with visibility — tag everything, use Cost Explorer, set budgets
- Go after the biggest items first — compute is usually 60%+ of the bill
- Automate scale-down — if nobody’s using it at night, turn it off
- Right-size based on data — not guesses. Use Compute Optimizer and Datadog metrics
- Choose the right pricing model — Savings Plans for stable, Spot for interruptible, On-Demand for unpredictable
- Eliminate waste — monthly audit for orphaned volumes, old snapshots, idle load balancers
- Make cost visible to teams — dashboards showing each team’s spend. People optimize what they can see
- Never sacrifice security or reliability — a £500/month saving that causes a £50,000 outage is not a saving
Tools Cheat Sheet
| Tool | What It Does | Cost |
|---|---|---|
| AWS Cost Explorer | Analyse spend by service, team, time | Free |
| AWS Budgets | Set spend thresholds, get alerts | First 2 free |
| AWS Cost Anomaly Detection | ML-based unexpected spend alerts | Free |
| AWS Compute Optimizer | Right-sizing recommendations | Free |
| Karpenter | EKS node optimization and consolidation | Free (open source) |
| kube-downscaler | Scale pods to zero outside business hours | Free (open source) |
| Kubecost | Per-pod, per-namespace cost visibility in K8s | Free tier available |
| Spot Advisor | Interruption rates by instance type/region | Free (AWS tool) |
| Trusted Advisor | Cost checks (requires Business support plan) | Included with support |
Conclusion
FinOps isn’t about being cheap — it’s about being smart. Every pound saved on idle infrastructure can be invested in better features, more resilience, or faster delivery. The best part: most of these optimizations are low-risk, low-effort, and high-impact. Start with the quick wins (lifecycle policies, overnight scaling, gp2→gp3), then work toward the strategic wins (Savings Plans, Karpenter, architecture changes).
The goal is a culture where cost is visible, waste is eliminated automatically, and every team owns their spend.
Want to discuss FinOps strategies for your organisation? Connect with me on LinkedIn.

