CloudThinker Use Case: Global AWS FinOps & Cost Optimization
Nobody remembered who launched the m5.xlarge in ap-southeast-1.
It had been running for nine months. CPU utilization: 0.2 percent. Memory: negligible. Network traffic: the occasional health-check ping. It was a ghost, a server doing nothing for $120 a month, inside a $4,350 monthly AWS bill that nobody could fully explain.
Linh, the operations lead at a mid-size SaaS company we'll call PeakStack, found it on a Tuesday afternoon. She wasn't looking for it. She was trying to answer a simpler question: why had the AWS bill gone up 20 percent in a quarter with no new product launches?
The answer wasn't one thing. It was thirty-one things.
Why manual cost audits don't keep up
PeakStack's AWS environment grew the way most do. A developer spins up an instance for a proof of concept. A database gets provisioned "just in case." Elastic IPs get allocated during a migration and never released. Nobody means to waste money, but across 21 regions waste builds up like sediment.
Linh had tried manual audits. She'd spend a Friday afternoon in the EC2 console, region by region, checking utilization. By ap-south-1 she was exhausted and it was 6 p.m. The next Friday she'd start over, because the environment had changed. The reasons that approach fails are structural:
The console is regional, the bill isn't. Each region is a separate view. A person audits the regions they remember to open, and the ghost sits in the one they didn't.
Cost Explorer shows where, not why. It breaks spend down by service, region and tag. When half the resources have no owner tag, it can tell you EC2 in Singapore costs too much, and nothing about which instance or whose it is.
Averages hide the truth. A 30-day average CPU of 3 percent could be an idle box or a nightly batch job that pegs the CPU for an hour. Right-sizing on averages either misses waste or breaks a workload.
Recommendations don't act. AWS Trusted Advisor and Compute Optimizer list candidates. Each one still needs someone to check the history, find the owner, confirm it's safe and make the change. That's the part that never fits into a Friday afternoon.
The audit is a snapshot. Cleaned up in March, drifting again by May. Without something watching continuously, savings leak back in.
The scan that changed the conversation
When Linh connected CloudThinker to PeakStack's AWS accounts, it did what she couldn't: a full pass across all 21 regions, every service, every resource.
Discovery results
- 107 AWS resources inventoried and analyzed
- 31 optimization opportunities identified
- Potential savings: $1,956/month (~$23,500/year)
These weren't scenarios from a best-practices checklist. Each one was a specific resource in a specific region with a dollar amount attached.
01Compute
Idle and oversized
An m5.xlarge at 0.2% CPU for nine months, and instance types nobody revisited after launch.
02Storage
Orphans and one tier
508 GB of unattached EBS, and S3 buckets with no lifecycle or tiering policy.
03Network
Forgotten addresses
Six Elastic IPs allocated during a migration and never released.
04Commitments
On-demand by default
Stable workloads with no Reserved Instances or Savings Plans behind them.
The ghost, end to end
01Inventory
Every region
All resources in all 21 regions, not the two a person reaches by Friday evening.
02Evidence
Utilization
CPU, memory and network over weeks, read for peaks, not averages.
03Context
Owner and purpose
Tags, launch history and dependencies, to rule out standby and batch use.
04Approved
Change
Stop, resize, release or delete, each with a named approver.
05Close
Verify
Confirms the saving shows up on the next bill and nothing broke.
The m5.xlarge is a good example of why each finding needs evidence before anyone acts on it.
1. Name the readings. A box at 0.2 percent CPU could be three things. It's abandoned. It's a warm standby for disaster recovery that's supposed to sit idle. Or it runs a batch job so rarely that averages hide it. Each reading predicts different data, and only the first one means "shut it down."
2. Check peaks, not averages. The daily maximum over 90 days tells the batch story apart from the idle one:
aws cloudwatch get-metric-statistics \
--region ap-southeast-1 \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0abc123example \
--start-time 2025-07-01T00:00:00Z \
--end-time 2025-10-01T00:00:00Z \
--period 86400 \
--statistics Maximum
The daily maximum never went above a few percent. No batch spikes, so the third reading drops out.
3. Look for an owner and a purpose. No Owner or Project tags. No load balancer target group, no Auto Scaling group, no Route 53 record pointing at it, nothing in the DR runbook. CloudTrail event history only keeps 90 days, so the original RunInstances call was already gone unless a trail had been shipping events to S3. That's why nobody remembered: the record had aged out along with the memory. Not a standby, then. Just abandoned.
4. Propose with a way back. The proposal wasn't "terminate." It was: snapshot the volume, stop the instance, wait two weeks for anyone to notice, then terminate. Linh approved it in chat. Nobody noticed.
The same pattern ran for every finding. The rest of the sweep used commands like these, applied across every region:
for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
# Unattached EBS volumes
aws ec2 describe-volumes --region "$r" \
--filters Name=status,Values=available \
--query 'Volumes[].[VolumeId,Size,CreateTime]' --output text
# Elastic IPs not associated with anything
aws ec2 describe-addresses --region "$r" \
--query 'Addresses[?AssociationId==null].[PublicIp,AllocationId]' --output text
done
The ghost and its friends
The m5.xlarge was the most dramatic find. Linh started calling it "the ghost" because it had no owner, no purpose, and nobody noticed when it was finally shut down. It had company:
Storage waste was hiding in plain sight. Two EBS volumes totaling 508 GB sat unattached, orphaned during a migration eight months earlier, at $50.80 a month. S3 buckets across several accounts had no Intelligent-Tiering or lifecycle policy, so data read once a year sat in the same storage class as data read every hour. Automated tiering was estimated to save $300 to $400 a month.
Compute was chronically oversized. Beyond the ghost, the analysis found a pattern: production workloads still on the instance types picked at setup and never revisited. Reserved Instances for stable workloads and Spot for fault-tolerant ones could save another $400 and $150 a month.
Network costs leaked quietly. Six Elastic IPs sat unassociated, at $3.60 a month each. Small on their own, but a symptom of the bigger problem: resources provisioned and forgotten.
Quantified results
| Metric | Current | Optimized | Savings |
|---|---|---|---|
| Monthly Cost | $4,350 | $3,261 | $1,089 (25%) |
| Annual Cost | $52,200 | $39,132 | $13,068 |
| ROI Timeline | - | - | Immediate to 3 months |
A 25 percent reduction, from finding and fixing what was already there. No re-architecture, no migration project.
The implementation playbook
01Week 1 to 2
Quick wins
Release Elastic IPs, delete orphaned volumes, turn on S3 Intelligent-Tiering. No downtime.
02Month 1 to 3
Right-size and commit
Resize EC2, buy Reserved Instances for stable load, clean old snapshots.
03Month 3 to 6
Architecture
Savings Plans, Spot for batch, cross-region data transfer.
Linh didn't try to fix everything at once. She put the 31 opportunities into three phases, starting with the ones that took the least effort and carried the least risk.
Quick wins (week 1 to 2)
- Release 6 unassociated Elastic IPs: $21.60/month saved
- Delete 2 idle EBS volumes: $50.80/month saved
- Enable S3 Intelligent-Tiering: $300 to $400/month saved
These took less than a day and needed no downtime. The Elastic IPs were released in minutes. The EBS volumes were deleted the same afternoon, once they were confirmed orphaned. Intelligent-Tiering went on with one lifecycle rule per bucket:
aws s3api put-bucket-lifecycle-configuration \
--bucket peakstack-exports \
--lifecycle-configuration '{
"Rules": [{
"ID": "to-intelligent-tiering",
"Status": "Enabled",
"Filter": {},
"Transitions": [{ "Days": 0, "StorageClass": "INTELLIGENT_TIERING" }]
}]
}'
Medium term (month 1 to 3)
- Right-size EC2 instances: $120/month saved
- Reserved Instances for stable workloads: $400/month saved
- Clean up old snapshots: $50/month saved
These needed more analysis. Right-sizing meant watching each replacement instance for a week to confirm performance held. Reserved Instance purchases meant forecasting how stable each workload would be over the commitment term.
Long term (month 3 to 6)
- Adopt Savings Plans strategically: $200/month saved
- Spot Instances for batch workloads: $150/month saved
- Optimize cross-region data transfer: $100/month saved
These were architecture decisions: which workloads could survive a Spot interruption, which regions should host which data, and how to size Savings Plans for coverage without overcommitting.
Top 3 highest-ROI actions
1. S3 Intelligent-Tiering
- Effort: Low (1 to 2 hours)
- Savings: $300 to $400/month
- Risk: Low (AWS moves objects between tiers; there's a small per-object monitoring fee, and objects under 128 KB aren't auto-tiered)
- ROI: Immediate
2. EC2 right-sizing
- Effort: Medium (4 to 8 hours)
- Savings: $120/month
- Risk: Low (needs a week of monitoring)
- ROI: 1 week
3. Reserved Instances
- Effort: Medium (planning required)
- Savings: $400/month
- Risk: Low (commitment-based)
- ROI: Immediate after purchase
Why this beats the traditional approach
| Manual audit | With CloudThinker | |
|---|---|---|
| Coverage | The regions someone opens before 6 p.m. | All 21 regions, every service |
| Evidence | Averages from a console graph | Peaks over weeks, with owner and dependency checks |
| Recommendations | A list to work through later | A proposal per finding, ready to approve |
| Safety | Delete and hope | Snapshot, stop, wait, then remove |
| Cadence | A Friday afternoon, when there is one | Continuous, so waste is caught when it appears |
| Time | 40+ hours a month of manual analysis | Minutes to review and approve |
The deeper difference is who does the checking. A recommendation engine can tell you an instance looks idle. Someone still has to rule out the standby and the monthly batch job, find the owner and plan the rollback. That checking is what used to fill Linh's Fridays, and it's what CloudThinker does before the proposal reaches her.
Gated by design
Nothing in PeakStack's account changed without a person saying yes. Reads (inventory, metrics, billing, tags) were enough for every finding. Writes, from releasing an Elastic IP to buying a Reserved Instance, go through Auto Mode, so you decide what runs on its own and what waits for approval. Commitment purchases like Reserved Instances and Savings Plans are the kind of decision most teams want a person to sign off.
The all-hands that told a different story
Six weeks after the first scan, Linh presented at PeakStack's monthly all-hands. She showed two numbers: $4,350, what they'd been spending, and $3,261, what they were spending now. The story she told wasn't about the savings. It was about visibility.
"We had 107 resources across 21 regions," she told the team. "Before CloudThinker, I could audit maybe two regions in an afternoon. Now I can see all of them, all the time. The ghost instance in Singapore was running for nine months. That does not happen anymore."
The engineering team's reaction said a lot. Nobody was embarrassed about the waste. They were relieved someone had finally built a system to catch it. Provisioning habits didn't change overnight, but visibility did. Developers started checking CloudThinker's recommendations before spinning up new resources, and the monthly review went from a blame session to a planning session.
Annual savings: $13,068. Time saved: 40+ hours per month of manual analysis. The real outcome was simpler than either number: PeakStack finally knew where its money was going.
Getting started
1. Connect your AWS accounts read-only. One role per account, or the management account of your AWS Organization. Nothing is changed until you say so.
2. Ask for the baseline.
"Scan every region in all connected AWS accounts. List idle or oversized instances, unattached volumes, unassociated Elastic IPs and S3 buckets with no lifecycle policy. Put a monthly dollar figure on each one and attach the utilization evidence. Propose only, change nothing."
3. Work the quick wins, then add a policy.
"For non-production accounts only, release Elastic IPs that have been unassociated for more than 30 days and post what you released. Everything in production still comes to me for approval."
Related reading
- Optimize: continuous cloud cost optimization
- Introducing CloudThinker Optimize
- Auto Mode: you decide what runs on its own
- How an Australian telematics provider automates day-to-day operations
Want to see what's hiding in your own regions? Book a discovery call.
