Overview
Most cloud cost problems aren't about usage being inherently expensive; they're about provisioned capacity sitting idle or oversized relative to what's actually needed, compounding silently because nothing forces a team to revisit a sizing decision made months ago. Cost optimization is the discipline of closing that gap: rightsizing resources to observed load, matching pricing models (on-demand, committed-use, spot) to how predictable and interruption-tolerant a workload actually is, and eliminating genuinely idle spend (unattached storage volumes, forgotten test environments) that provides zero value at any size. None of this is a one-time project, usage patterns and workloads change, so the waste that gets cleaned up today starts accumulating again immediately. Idle compute and storage are the obvious leaks; data-processing charges hidden inside network architecture are the less obvious kind, see The AWS Bill Spiked and Nobody Had Launched Anything New for a real NAT Gateway cost investigation.
Quick Reference
| Lever | Addresses | Typical savings |
|---|---|---|
| Rightsizing | Over-provisioned steady-state resources | Often the single biggest win |
| Committed-use discounts | Predictable, continuous workloads | 30–60% vs. on-demand |
| Spot/preemptible instances | Interruption-tolerant, flexible workloads | Up to 70–90% vs. on-demand |
| Autoscaling | Load-variable workloads | Avoids paying for unused peak capacity |
| Idle-resource cleanup | Unattached volumes, forgotten environments | 100% of that specific waste |
Syntax
# Autoscaling tied to actual observed load, not a static guess
resource "aws_autoscaling_policy" "scale_up" {
autoscaling_group_name = aws_autoscaling_group.app.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ASGAverageCPUUtilization"
}
target_value = 60.0
}
}Examples
# Find genuinely idle spend - an unattached volume costs the same
# whether it's attached to a running instance or sitting orphaned.
aws ec2 describe-volumes --filters Name=status,Values=available \
--query 'Volumes[*].[VolumeId,Size,CreateTime]'# NAT Gateway data processing is billed per GB and doesn't show up as a
# line item until Cost Explorer, this pulls the actual bytes flowing
# through a specific gateway over the last day.
aws cloudwatch get-metric-statistics \
--namespace AWS/NATGateway --metric-name BytesOutToDestination \
--dimensions Name=NatGatewayId,Value=nat-0123456789abcdef0 \
--start-time "$(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%S)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%S)" --period 86400 --statistics SumTag everything, from day one
Cost attribution (which team, which project, which environment) is what makes rightsizing and cleanup decisions actionable at all. Retrofitting tags onto years of untagged resources is far more expensive than enforcing them at creation time.
Visual Diagram
Common Mistakes
- Provisioning for peak load and never scaling back down, leaving steady-state capacity idle the vast majority of the time.
- Using on-demand pricing for a workload that's been running continuously and predictably for months, the exact case committed-use discounts are built for.
- Leaving orphaned resources (unattached storage volumes, unused load balancers, forgotten test environments) running indefinitely because nothing alerts on pure waste the way it alerts on an outage.
- Setting autoscaling minimums far above actual baseline load "to be safe," which quietly defeats much of autoscaling's cost benefit.
- Routing traffic to AWS services like S3 or DynamoDB through a NAT Gateway instead of a VPC Gateway Endpoint. NAT Gateway bills a per-GB data processing charge on top of its hourly cost, on high-volume traffic this can dwarf the compute bill it's attached to, while a Gateway Endpoint for S3/DynamoDB traffic costs nothing extra and keeps that traffic off the NAT Gateway entirely.
Performance
- Aggressive cost-cutting can trade away real performance margin, rightsizing too tightly against average load leaves no headroom for legitimate spikes, causing degradation exactly when it matters most.
- Spot/preemptible instances introduce interruption as a real operational concern; workloads using them need to handle reclamation gracefully (checkpointing, retry logic) or the "savings" show up as reliability cost instead.
- Committed-use discounts lock in capacity assumptions for their term, a workload that shrinks significantly during that period turns a discount into paid-for-but-unused capacity, so commitments should track only genuinely stable baseline usage.
Best Practices
- Tag every resource with owner/team/environment from creation, since cost attribution is what makes every other optimization actionable.
- Rightsize on a recurring cadence using actual utilization data, not a one-time pass, usage patterns drift.
- Match pricing model to workload predictability: committed-use for stable steady-state, spot for interruption-tolerant and flexible, on-demand for genuinely unpredictable or short-lived needs.
- Set up automated detection for idle resources (unattached volumes, empty load balancers) so waste gets caught continuously, not only during periodic manual audits.
- I use VPC Gateway Endpoints (free, no data-processing charge) for S3/DynamoDB traffic instead of routing it through a NAT Gateway; reserve NAT Gateway for traffic that genuinely needs to reach the public internet.
- I check a NAT Gateway's
BytesOutToDestinationCloudWatch metric before a bill surprise forces the investigation, per-GB processing charges are invisible in the console until they show up as a lump sum in Cost Explorer.