Cloud Cost Engineering: FinOps Without the Buzzwords
Studies consistently find that 30–40% of cloud spend is wasted: overprovisioned instances nobody has resized, stopped EC2 instances accumulating EBS charges, NAT Gateways processing gigabytes of traffic that could route through free VPC endpoints, development environments running on nights and weekends, storage buckets with data nobody has accessed in three years. The tooling to find and fix this has improved significantly, but the tooling is not the hard part.
The hard part is organizational. Cloud cost optimization requires someone with the authority to change things, the data to know what to change, and the processes to prevent the waste from accumulating again. Without all three, you run a cost review, make some changes, declare victory, and six months later the bill is back where it started.
This post covers the technical mechanics of AWS cost reduction — commitment discounts, rightsizing, idle resource cleanup, data transfer, storage tiering — and the organizational practices that make the savings stick.
Understand Your Bill Before Optimizing It
The single most common mistake in cost optimization is jumping to solutions before understanding the actual cost distribution. “We should buy Reserved Instances” is not a useful insight; “our production RDS cluster runs 24/7 and costs $4,200/month at on-demand pricing; three-year no-upfront Savings Plans would save $2,400/month” is.
Cost Explorer fundamentals
AWS Cost Explorer (Billing → Cost Explorer) is free and provides 13 months of cost history. The filters and grouping dimensions that matter most for initial analysis:
Group by: Service → find which services drive the bill
Group by: Usage Type → find what within a service (data transfer, instance-hours, storage)
Group by: Tag (team/env) → find which teams or environments spend the most
Filter: Charge Type = Usage → exclude credits, fees, taxes for clean comparison
For EC2 specifically, break down by:
- Instance type: which types and families are running most hours
- Purchase option: on-demand vs reserved vs spot vs savings plan (shows commitment coverage gaps)
- Region: identify unexpected cross-region activity
Save useful filter configurations as Cost Explorer bookmarks. The analysis you run during this cost review is the same one you will run monthly — save it once.
Cost and Usage Report for deep analysis
Cost Explorer covers 90% of use cases. When you need more granular data (per-resource-ID breakdown, hourly granularity, custom dimensions), enable the Cost and Usage Report (CUR):
Billing → Cost and Usage Reports → Create report
Report name: cur-hourly
Include resource IDs: checked
Delivery: S3 bucket in your account
Format: Parquet (for Athena queries)
Granularity: Hourly
Query CUR with Athena:
|
|
This gives you the specific resource IDs costing the most, which is what you need to act on rather than aggregate service totals.
Commitment Discounts
The single highest-leverage cost action for stable workloads is buying commitment discounts. AWS on-demand pricing is deliberately expensive — it is the price you pay for zero commitment and infinite flexibility. For any workload running more than ~600 hours per month (about 83% uptime), some form of commitment is almost certainly cheaper.
Savings Plans: the modern default
Savings Plans are a dollar-per-hour spend commitment that AWS applies as a discount against on-demand charges. You commit to spending, say, $5.00/hour on compute, and AWS gives you 40–66% off on-demand rates on all compute resources up to that hourly spend. Above the commitment, charges revert to on-demand.
Three types:
| Type | Applies to | Discount vs on-demand | Flexibility |
|---|---|---|---|
| Compute Savings Plans | EC2, Lambda, Fargate, ECS | Up to 66% | Any instance family, size, region, OS |
| EC2 Instance Savings Plans | EC2 only (specific family + region) | Up to 72% | Any size and OS within the family |
| SageMaker Savings Plans | SageMaker only | Up to 64% | Any instance type, region |
Compute Savings Plans are the right default for most organizations. They apply across instance families and regions, which means a commitment made against your current m5 fleet still applies if you migrate to m7i or move a workload to a different region. The 6% lower discount compared to EC2 Instance Savings Plans is worth the flexibility in nearly every case.
EC2 Instance Savings Plans make sense when you are very confident a specific instance family in a specific region is stable for 1–3 years. A production database fleet on r6i in us-east-1 that has been running the same way for 18 months is a candidate.
Commitment terms are 1-year or 3-year. Payment options are all-upfront (maximum discount), partial-upfront, or no-upfront. The discount difference between all-upfront and no-upfront is typically 5–10 percentage points — a meaningful spread if you have the capital.
How much to commit: AWS Savings Plans recommendations in Cost Explorer calculate the optimal commitment based on your trailing 7, 30, or 60 days of usage. The recommendation targets a coverage percentage you specify. Start at 70% coverage of your on-demand baseline — this covers the stable floor of your usage without overcommitting on variable peaks. Adjust after observing the savings for a month.
Cost Explorer → Savings Plans → Recommendations
Savings Plans type: Compute Savings Plans
Term: 1 Year
Payment: No upfront
Look-back period: 30 days
→ Review estimated monthly savings and coverage percentage
Reserved Instances: where they still make sense
Reserved Instances commit to specific instance attributes (type, region, OS, tenancy) in exchange for up to 72% off on-demand. Standard RIs are inflexible — you cannot change the instance type after purchase. Convertible RIs allow exchanges but provide lower discounts (~54%).
RIs remain the right tool in specific cases:
- RDS: Savings Plans do not cover RDS. RDS Reserved Instances provide up to 69% discount and are the only commitment discount available for database instances.
- ElastiCache, Redshift, OpenSearch: similar situation — Savings Plans do not apply; service-specific RIs do.
- Exact instance type stability: if you are certain a workload will run on
m5.4xlargeinus-east-1for three years, a Standard RI provides the maximum discount.
For EC2 and compute workloads where Savings Plans apply, RIs are generally not worth the management overhead.
Spot Instances
Spot is spare EC2 capacity at 70–90% discount, with the trade-off that AWS can reclaim it with a 2-minute warning. The right tool for:
- Stateless, fault-tolerant workloads
- Batch processing (ML training, data pipelines, CI/CD runners)
- Dev/test environments
- EKS worker nodes managed by Karpenter (handles interruption automatically)
The mistake is treating Spot as risky and avoiding it for workloads that are actually tolerant of interruption. A CI pipeline that retries jobs has nothing to fear from a Spot interruption. An EKS cluster with proper PodDisruptionBudgets handles Spot node termination routinely. The organizations that save 40–60% on compute are the ones that systematically identify which workloads can tolerate interruption and run those on Spot.
Commitment strategy in practice
A practical three-tier commitment strategy:
Baseline (stable, always-on workloads)
└── Compute Savings Plans: 70% of baseline on-demand spend
└── RDS/ElastiCache: service-specific Reserved Instances
Variable layer (scales with demand)
└── On-demand: covered by Savings Plans above the baseline
Burst/batch layer
└── Spot: 70–90% discount for interruptible workloads
Review and adjust commitments quarterly. As you rightsize instances or retire workloads, your on-demand baseline changes. Overcommitting Savings Plans means paying for hours you are not using — the excess does not roll forward.
Tagging: The Foundation of Cost Allocation
Without tags, your Cost Explorer shows “EC2: $142,000/month” with no visibility into which team, service, or environment drives that number. With consistent tagging, you see “team:payments production: $23,400, team:payments staging: $4,100, team:data-platform production: $31,200.” That is the difference between a cost review and a cost conversation.
Mandatory tag taxonomy
Define a small set of required tags that every resource must carry:
| Tag key | Values | Purpose |
|---|---|---|
team |
payments, platform, data, security |
Owner for chargeback |
environment |
production, staging, development |
Cost split by env |
service |
checkout-api, fraud-engine, kafka |
Service-level cost |
cost-center |
Finance GL codes | Finance chargeback |
Keep the required set small. Four mandatory tags enforced consistently beats twenty tags with 60% compliance. Add optional tags (project, ticket, etc.) but do not make them required — enforcement complexity scales with requirement count.
Enforcing tags with Tag Policies and SCPs
AWS Tag Policies enforce tag keys and value patterns across an organization:
|
|
Attach this policy at the organization root or at the OU level. Resources that violate the policy are flagged in the Tag Policy console — not blocked by default (you can enable enforcement mode, but it breaks existing Terraform/CloudFormation that does not include the tags, so roll it out incrementally).
For new infrastructure via Terraform, enforce tags at the provider level:
|
|
default_tags applies to every resource created by the provider without requiring explicit tags blocks on each resource. This catches the resources engineers forget to tag.
Finding untagged resources
|
|
The Tag Editor in the AWS console provides a bulk interface to add tags to existing untagged resources.
Activate tags for Cost Allocation
Creating tags on resources does not automatically make them available in Cost Explorer. Activate them:
Billing → Cost allocation tags → User-defined → Activate
Select: team, environment, service, cost-center
Activation takes up to 24 hours to propagate. Historical data before activation is not retroactively tagged.
Rightsizing
Rightsizing is matching instance size to actual workload requirements. The typical finding: 20–40% of EC2 instances in an organization are significantly overprovisioned — running at 5–15% average CPU utilization on instances sized for peak load that rarely arrives.
AWS Compute Optimizer
Compute Optimizer analyzes CloudWatch metrics (CPU, memory if the CloudWatch agent is installed, network, disk) and recommends instance changes:
Compute Optimizer → EC2 instances
Filter: Finding = Over-provisioned
Sort by: Estimated monthly savings descending
The output shows:
- Current instance type and cost
- Recommended instance type and estimated cost
- CPU and memory utilization percentiles (P50, P99)
- Estimated monthly savings
Prioritize recommendations by savings, not count. One r5.16xlarge recommendation may save more than 50 t3.small recommendations combined.
What Compute Optimizer does not tell you
Compute Optimizer looks at CPU and (optionally) memory. It does not measure:
- Network throughput: an instance at 5% CPU may be at 90% network bandwidth
- IOPS: a database instance may be CPU-idle but disk-bound
- Application-level performance: reducing instance size may increase response times even if CPU headroom appears to exist
For EC2 running databases or network-intensive applications, validate recommendations with application-level metrics (query latency, error rates) on a non-production instance before applying to production.
Memory metrics require the CloudWatch agent
By default, CloudWatch does not collect memory utilization from EC2 instances — the hypervisor cannot see inside the guest. Without memory metrics, Compute Optimizer cannot assess memory utilization and marks recommendations as having “insufficient data.”
Install the CloudWatch agent via SSM:
|
|
Configure it to collect memory metrics:
|
|
gp2 to gp3 migration
EBS gp2 volumes are almost always more expensive than gp3 for the same size. gp3 provides 3,000 IOPS and 125 MB/s baseline throughput at 20% lower cost per GB, with IOPS and throughput independently configurable up to 16,000 IOPS and 1,000 MB/s. gp2 scales IOPS with volume size (3 IOPS/GB) and costs more.
Migrate all gp2 volumes to gp3:
|
|
For Terraform-managed volumes, update the type:
|
|
Hunting Idle and Zombie Resources
Industry estimates put idle resource waste at 30–40% of total cloud spend. In practice the distribution is skewed — most of the waste is concentrated in a small number of expensive resources that nobody is monitoring.
EC2: stopped instances
Stopped EC2 instances do not incur instance-hour charges, but their attached EBS volumes continue billing. A r5.4xlarge stopped “temporarily” six months ago still pays for its 500 GB gp3 root volume and data volume at $40+/month.
|
|
For each stopped instance, determine: is this a deliberate pause or forgotten? Check CloudTrail for when it was stopped and who stopped it. If it has been stopped for more than 30 days with no recent activity, raise it with the owner.
Unattached EBS volumes
EBS volumes that exist but are not attached to any instance are paying for nothing:
|
|
Create a snapshot before deleting, in case the data is needed:
|
|
Unused Elastic IPs
Every Elastic IP not associated with a running instance costs $0.005/hour (~$3.65/month). Individually small, collectively significant in large organizations:
|
|
Idle load balancers
ALBs and NLBs without target groups, or with target groups containing no healthy targets, still cost $16–22/month each:
|
|
Old snapshots
EBS snapshots accumulate silently. A backup policy that creates daily snapshots and never deletes them will have years of snapshots for decommissioned instances. At $0.05/GB-month, 10 TB of forgotten snapshots costs $500/month.
|
|
AWS DLM (Data Lifecycle Manager) automates snapshot retention policy:
|
|
RDS idle instances
Development and staging RDS instances often run 24/7 even though they are only used during business hours. Stopping them nights and weekends saves 65% of the cost (RDS stopped instances still charge for storage):
|
|
Data Transfer: The Hidden Bill
Data transfer charges are opaque, widely misunderstood, and responsible for unexpectedly large line items. The relevant pricing:
| Transfer type | Cost |
|---|---|
| Inbound from internet | Free |
| Outbound to internet | $0.09/GB (first 10 TB/month) |
| Cross-AZ within region | $0.01/GB each direction |
| Cross-region | $0.02/GB (varies by region pair) |
| NAT Gateway processing | $0.045/GB |
| Same-AZ | Free |
| VPC endpoints (Gateway: S3, DynamoDB) | Free |
| VPC Interface Endpoints | $0.01/GB |
NAT Gateway: the most common surprise
A NAT Gateway processing 10 TB/month costs $450/month in data processing charges alone (plus $32/month per gateway in hourly fees). Common patterns that drive NAT Gateway costs unnecessarily:
S3 and DynamoDB traffic through NAT: All traffic to S3 and DynamoDB should route through free Gateway endpoints. The VPC endpoint takes 5 minutes to configure and eliminates all data processing charges for that traffic:
|
|
Cross-AZ data transfer through NAT: if an EC2 instance in us-east-1b makes requests that route to a NAT Gateway in us-east-1a, each byte traverses the cross-AZ link twice (once to NAT, once back), paying $0.02/GB in cross-AZ fees plus $0.045/GB in NAT processing fees. Place NAT Gateways in each AZ and update route tables so each private subnet routes to the NAT Gateway in its own AZ.
ECR image pulls through NAT: Docker image pulls from ECR through NAT Gateway can add hundreds of dollars to the bill for clusters that frequently pull large images. Create VPC Interface Endpoints for ECR:
|
|
Finding cross-AZ traffic
Cross-AZ charges ($0.01/GB each direction) accumulate invisibly. The resource IDs causing cross-AZ traffic are not visible in Cost Explorer; you need CUR or VPC Flow Logs analysis.
A rough indicator: if your EKS pods are distributed across AZs but your RDS read replicas, ElastiCache nodes, or other backend services are concentrated in fewer AZs, every cross-AZ request pays the toll. Use availability zone affinity for pods to route requests to backends in the same AZ where possible.
S3 Cost Optimization
S3 costs have three components: storage ($0.023/GB/month for S3 Standard), requests ($0.0004 per 1,000 GET requests), and data retrieval (free for Standard, varies for other tiers). The lever is storage class.
S3 Intelligent-Tiering
S3 Intelligent-Tiering automatically moves objects to cheaper storage classes based on access patterns. Objects not accessed for 30 days move to Infrequent Access; not accessed for 90 days move to Glacier Instant Retrieval; not accessed for 180 days move to Glacier Flexible Retrieval.
| Tier | Cost/GB/month | Retrieval |
|---|---|---|
| Frequent Access | $0.023 | Immediate |
| Infrequent Access | $0.0125 | Immediate |
| Glacier Instant Retrieval | $0.004 | Immediate |
| Glacier Flexible Retrieval | $0.0036 | Minutes to hours |
Intelligent-Tiering costs $0.0025 per 1,000 objects monitored per month (for objects > 128 KB). For buckets with large objects accessed infrequently, this pays for itself within the first month.
Apply Intelligent-Tiering to appropriate buckets:
|
|
For known-cold data (compliance archives, old log files), skip Intelligent-Tiering and go directly to Glacier:
|
|
Expire old log data
Application and access logs in S3 are rarely accessed after 90 days but accumulate indefinitely without a lifecycle policy. Set expiration policies on all log buckets:
|
|
Budgets and Alerts
The most common cost surprise is discovering a large bill at the end of the month. Budgets alert before that happens.
|
|
Set both actual (you have spent 80% of budget) and forecasted (you are projected to exceed budget) alerts. Forecasted alerts give you time to act before the overage occurs.
Use anomaly detection for sudden spikes that stay within monthly budget but represent unexpected behavior:
Cost Explorer → Cost Anomaly Detection → Create monitor
Monitor type: AWS services
Alert threshold: $500 or 20% above expected (whichever is higher)
Notification: platform team email
The Organizational Work
Technical optimization is the easier half. The harder half is building the processes that prevent costs from drifting back up.
Showback before chargeback
Showback means showing teams their cost data without billing them for it. Chargeback means actually transferring the cost to their budget. Start with showback — get teams looking at their costs and building intuition for what they are responsible for, before the political complexity of internal billing.
A weekly cost email per team (automated from Cost Explorer data via Lambda) is more effective than a quarterly review: teams see costs when they can still act on them, the feedback loop is short, and small surprises get addressed before they become large ones.
The “Cost is an engineering metric” framing
Cost alerts that go to a finance team and generate tickets for engineering are slower and more adversarial than cost data surfaced directly to the engineers making infrastructure decisions. The same engineer who launches a r5.16xlarge when a r5.4xlarge would suffice will make a different decision when they see the cost attribution weekly and know it appears in a dashboard their manager also reads.
Cost reviews with architecture context
The most useful cost optimization sessions combine cost data with architecture knowledge:
- “This RDS instance costs $4,200/month at 8% CPU. What is it actually doing?”
- “This NAT Gateway processed 50 TB last month. What is generating that traffic?”
- “These 12 EC2 instances have not had a connection in 45 days. Do they belong to the project that was cancelled?”
Without the architecture context, cost data is a spreadsheet. With it, cost data is a diagnostic tool.
Prioritizing the Work
Not all cost optimizations are worth the time to implement. A rough priority framework:
| Action | Typical savings | Effort | Do first? |
|---|---|---|---|
| Savings Plans purchase | 40–66% of stable compute spend | 30 minutes | Yes |
| gp2 → gp3 migration | 20% of EBS spend | 1–2 hours | Yes |
| S3 lifecycle policies | 50–80% of storage for cold data | 2–4 hours | Yes |
| Idle resource cleanup | Varies; often $500–5,000/month | 1 day | Yes |
| NAT Gateway VPC endpoints | Varies; often $200–2,000/month | 2–4 hours | Yes |
| RDS dev/staging scheduling | 65% of dev RDS cost | 2–4 hours | Yes |
| Rightsizing (EC2) | 20–40% of on-demand compute | Days to weeks | After above |
| Spot migration (EKS) | 60–80% of node group cost | Days | After rightsizing |
| Cross-AZ traffic reduction | Varies | Weeks | Advanced |
The first six items on this list are mechanical and low-risk. Do them before optimizing architecture. The savings from these actions are permanent — once a lifecycle policy is applied or a Savings Plan is purchased, it runs without ongoing attention.
Rightsizing and Spot migration require more care but also return more. After the mechanical items, these are where the sustained savings come from.
The FinOps literature is full of frameworks, maturity models, and capability assessments. None of that matters until you have done the mechanical work and built the tagging foundation. Start there.
Comments