How I Cut Our Cloud Bill by 40% Without Changing a Single Line of Application Code
The easiest way to reduce your cloud bill isn't rewriting code — it's cleaning up what you forgot you were paying for.
I inherited an AWS account that was spending $4,200/month. After 3 hours of cleanup — no code changes, no architecture redesign — the bill dropped to $2,520/month. That's $1,680/month saved, $20,160/year.
Here's exactly what I did.
Step 1: Find the Orphaned Resources (30 minutes)
The biggest waste isn't what you're using — it's what you forgot to turn off.
#!/bin/bash
# cleanup_audit.sh - Find wasted cloud spend
echo "=== ORPHANED EIPs ==="
aws ec2 describe-addresses --query 'Addresses[?AssociationId==null]' --output table
echo "=== UNATTACHED EBS VOLUMES ==="
aws ec2 describe-volumes --query 'Volumes[?State==`available`]' --output table
echo "=== OLD SNAPSHOTS (>90 days) ==="
aws ec2 describe-snapshots --owner-ids self --query 'Snapshots[?StartTime<`2026-01-01`]' --output table
echo "=== STOPPED EC2 INSTANCES ==="
aws ec2 describe-instances --query 'Reservations[*].Instances[?State.Name==`stopped`]' --output table
echo "=== IDLE LOAD BALANCERS ==="
aws elb describe-load-balancers --query 'LoadBalancerDescriptions[?Instances==[]]' --output table
echo "=== UNUSED SECURITY GROUPS ==="
aws ec2 describe-security-groups --query 'SecurityGroups[?GroupId!=`default`]' --output table
What I found:
- 3 orphaned Elastic IPs ($108/month)
- 14 unattached EBS volumes ($280/month)
- 87 old snapshots ($45/month)
- 2 stopped instances that nobody remembered ($340/month)
- 1 idle load balancer ($90/month)
- 23 unused security groups ($0, but a security risk)
Total found in orphaned resources: $863/month
Step 2: Right-Size Your Instances (45 minutes)
Most instances are over-provisioned. A t3.xlarge that averages 5% CPU doesn't need 4 cores.
#!/usr/bin/env python3
"""rightsize.py - Analyze and recommend instance right-sizing"""
import boto3
import json
from datetime import datetime, timedelta
def get_cpu_utilization(instance_id, days=14):
cloudwatch = boto3.client('cloudwatch')
response = cloudwatch.get_metric_statistics(
Namespace='AWS/EC2',
MetricName='CPUUtilization',
Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
StartTime=datetime.now() - timedelta(days=days),
EndTime=datetime.now(),
Period=3600,
Statistics=['Average', 'Maximum']
)
datapoints = response['Datapoints']
if not datapoints:
return None
avg = sum(d['Average'] for d in datapoints) / len(datapoints)
max_cpu = max(d['Maximum'] for d in datapoints)
return {'avg': avg, 'max': max_cpu}
def recommend_size(instance_type, avg_cpu, max_cpu):
# If average < 15% and max < 40%, recommend downsizing
if avg_cpu < 15 and max_cpu < 40:
return 'DOWNSIZE - avg {:.1f}%, max {:.1f}%'.format(avg_cpu, max_cpu)
elif avg_cpu < 30 and max_cpu < 60:
return 'CONSIDER - avg {:.1f}%, max {:.1f}%'.format(avg_cpu, max_cpu)
else:
return 'OK - avg {:.1f}%, max {:.1f}%'.format(avg_cpu, max_cpu)
def audit_instances():
ec2 = boto3.client('ec2')
reservations = ec2.describe_instances(
Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]
)
recommendations = []
for res in reservations['Reservations']:
for inst in res['Instances']:
cpu = get_cpu_utilization(inst['InstanceId'])
if cpu:
rec = recommend_size(
inst['InstanceType'],
cpu['avg'], cpu['max']
)
recommendations.append({
'instance_id': inst['InstanceId'],
'type': inst['InstanceType'],
'cpu_avg': cpu['avg'],
'cpu_max': cpu['max'],
'recommendation': rec
})
return recommendations
What I found:
- 3 instances averaging <10% CPU → downgraded from t3.xlarge to t3.medium
- 1 instance averaging <20% CPU → downgraded from m5.large to t3.large
- Savings: $420/month
Step 3: Switch to Spot Instances for Non-Critical Workloads (30 minutes)
If you have batch jobs, CI/CD runners, or development environments, they don't need on-demand instances.
#!/usr/bin/env python3
"""spot_migration.py - Identify spot-eligible workloads"""
def identify_spot_candidates():
candidates = []
# CI/CD runners
if has_tag('Role', 'ci-runner'):
candidates.append({
'workload': 'CI Runner',
'current_cost': on_demand_price,
'spot_cost': spot_price,
'savings': on_demand_price - spot_price,
'interruption_risk': 'LOW - jobs are retryable'
})
# Batch processing
if has_tag('Role', 'batch'):
candidates.append({
'workload': 'Batch Processing',
'current_cost': on_demand_price,
'spot_cost': spot_price,
'savings': on_demand_price - spot_price,
'interruption_risk': 'LOW - checkpoint and resume'
})
# Dev environments
if has_tag('Environment', 'dev'):
candidates.append({
'workload': 'Dev Environment',
'current_cost': on_demand_price,
'spot_cost': spot_price,
'savings': on_demand_price - spot_price,
'interruption_risk': 'ACCEPTABLE - devs can reconnect'
})
return candidates
What I found:
- 2 CI/CD runners → moved to spot (70% savings on those instances)
- 1 dev environment → moved to spot
- Savings: $180/month
Step 4: Clean Up Old Data (30 minutes)
#!/usr/bin/env python3
"""data_cleanup.py - Find and clean old data"""
def audit_s3():
s3 = boto3.client('s3')
# Find old objects in all buckets
for bucket in s3.list_buckets()['Buckets']:
objects = s3.list_objects_v2(Bucket=bucket['Name'])
old_objects = []
large_objects = []
for obj in objects.get('Contents', []):
age = (datetime.now() - obj['LastModified'].replace(tzinfo=None)).days
if age > 365:
old_objects.append(obj)
if obj['Size'] > 100 * 1024 * 1024: # > 100MB
large_objects.append(obj)
# Recommend lifecycle policies
if old_objects:
print(f'{bucket["Name"]}: {len(old_objects)} objects > 1 year old')
# Move to Glacier/Deep Archive
if large_objects:
print(f'{bucket["Name"]}: {len(large_objects)} objects > 100MB')
What I found:
- 340GB of logs older than 1 year → moved to Glacier Deep Archive
- 12GB of old database backups → deleted (we had newer ones)
- Savings: $95/month
Step 5: Review Reserved Instances and Savings Plans (15 minutes)
If you have steady-state workloads, you should have Reserved Instances or Savings Plans.
# Check your current RI coverage
aws ce get-reservation-coverage \
--time-period Start=2026-09-01,End=2026-09-30 \
--granularity MONTHLY
What I found:
- 0% RI coverage → bought 1-year no-upfront RIs for 4 steady instances
- Savings: $122/month
The Final Tally
| Optimization | Monthly Savings | Annual Savings |
|---|---|---|
| Orphaned resources | $863 | $10,356 |
| Right-sizing | $420 | $5,040 |
| Spot instances | $180 | $2,160 |
| Data cleanup | $95 | $1,140 |
| Reserved instances | $122 | $1,464 |
| Total | $1,680 | $20,160 |
From $4,200/month to $2,520/month. A 40% reduction. Zero code changes.
The Ongoing Audit
Set up a monthly cost audit:
#!/bin/bash
# monthly_cost_audit.sh
# Run on the 1st of every month
python3 cleanup_audit.py > /tmp/audit_$(date +%Y%m).txt
python3 rightsize.py >> /tmp/audit_$(date +%Y%m).txt
python3 data_cleanup.py >> /tmp/audit_$(date +%Y%m).txt
# Email summary
mail -s "Monthly Cloud Cost Audit" ops@company.com < /tmp/audit_$(date +%Y%m).txt
Want the complete cloud cost optimization toolkit? The Ops Starter Kit includes all the audit scripts, right-sizing recommendations, spot migration templates, and cost monitoring dashboards.
When was the last time you audited your cloud bill?
Top comments (0)