The Monthly AWS Cost Review I Run for Clients (and My Own Accounts)
A repeatable FinOps checklist for contractors — CUR exports, tagging audits, and the conversation script when a client's bill jumps 40%.

The Monthly AWS Cost Review I Run for Clients
Early in my career I shipped a working environment for a client and felt good about it until the third monthly invoice. Steady-state spend was roughly triple what we'd discussed in planning. The client was polite about it, which made it worse — they'd budgeted headcount around a number I hadn't validated. We spent a week re-architecting instance sizes, fixing orphaned EBS volumes, and turning off a NAT Gateway pattern that had made sense in the diagram and not in the traffic profile.
That mistake is why every infrastructure contract I take now includes a cost review cadence in the SOW, even when the client doesn't ask for FinOps explicitly. Cost is a reliability attribute. An environment you can't afford to run is an environment that will be torn down under pressure.
This is the monthly playbook I run — for clients and for my own lab accounts.
Before the Review: What I Set Up on Day One
Cost reviews fail when tagging and ownership don't exist. On new engagements I push for:
- Mandatory tags — at minimum
Environment,Owner,CostCenter(orProject). AWS cost allocation tags only work if they're activated in the billing console. - Separate accounts or at least separate VPCs for prod vs. experimental work. "One account, one big bill" is how surprise spend hides.
- Budgets with alerts — AWS Budgets at 80% and 100% of expected monthly spend, emailed to a distribution list that includes me during the contract period.
I don't wait for month three to discover nobody tagged anything. Week one includes a tagging audit script (I use Resource Groups Tagging API + a simple CSV export) and a fifteen-minute call to agree on who gets paged when a budget fires.
The First Week of the Month: Data Pull
On the first business day of each month I pull:
- Cost Explorer — previous month total, compared to prior month and same month last year if available. Group by service, then by linked account, then by tag.
- Cost and Usage Report (CUR) — if the client is large enough to have it enabled, I slice by
line_item_usage_typeand look for new usage families. CUR is overkill for a $3k/month account; Cost Explorer is enough. - Trusted Advisor / Compute Optimizer — idle load balancers, underutilized RDS, old snapshots.
I export three charts and one table into a shared doc:
| Finding | Service | Est. monthly $ | Owner | Recommended action |
|---|---|---|---|---|
| Example | EC2 m5.2xlarge dev | $280 | platform team | Rightsize to m5.large |
| Example | Unattached EBS | $45 | unknown | Snapshot + delete after confirm |
The table is the deliverable clients actually read. Charts support it; the table drives decisions.
The Anomaly Pass
I look for week-over-week spikes, not just month totals. A 40% jump that happened in the last five days of the month can be invisible in a calm monthly average.
Common culprits I see in real accounts:
- NAT Gateway data processing — dev environments routing all egress through NAT when a VPC endpoint would suffice for S3/Dynamo.
- CloudWatch Logs ingestion — verbose application logging at
DEBUGin production. - Forgotten GPUs or large instances — someone sized for a load test and didn't tear down.
- Cross-AZ data transfer — chatty services across availability zones without need.
- S3 lifecycle gaps — intelligent-tiering not enabled, or incomplete multipart uploads accumulating.
For each anomaly I write one sentence of cause and one sentence of fix. Clients don't need a lecture on data transfer pricing; they need "here's the line item, here's what we do Tuesday."
The Client Conversation
I schedule a 30-minute review call or send an async Loom if time zones don't align — Seoul to US West is my normal constraint. Structure:
- Headline number — total spend, delta vs. plan, one sentence on whether we're in band.
- Top three drivers — services, not jargon.
- Decisions needed — items that require business input (e.g., "keep multi-AZ for this non-prod DB?").
- Actions I'll take without asking — safe optimizations already in the change window.
The script when spend is over budget: "We're $X over the run rate we modeled. Two-thirds is explainable by [specific change the client approved]. One-third is waste we can recover — here's the table."
Blameless tone matters. The goal is predictable bills, not winning an argument about who left an instance on.
What I Automate vs. What Stays Manual
I automate exports and Trusted Advisor pulls with a small scheduled Lambda or a monthly EventBridge job in the client's tooling account. I do not fully automate rightsizing without human review — shutting down the wrong RDS is worse than paying for it one more month.
For my personal lab account I use the same checklist on the 5th of each month. Lab accounts are where bad habits start; if I can't keep a $150/month sandbox clean, I shouldn't preach FinOps to clients.
When the Client Doesn't Want to Pay for Reviews
Some clients want "build only." I still run a one-time baseline review at handoff and document expected monthly run rate in the runbook. If they skip ongoing reviews, the SOW says ongoing cost drift is their operational responsibility after sign-off. That sentence has prevented two awkward renewals where they expected free FinOps forever.
Summary Checklist
- Tags and budgets live on day one, not after the first bill shock.
- Monthly: Cost Explorer by service → anomaly pass → action table → 30-minute review.
- Every recommendation ties to a line item dollar amount.
- Safe fixes ship; risky fixes get a decision row.
The environment I built that tripled the client's bill taught me that working and affordable are both part of done. This playbook is how I make affordability visible before finance escalates.
Tagging Remediation: The One-Time Sprint
When I inherit an account with no tags, I don't try to fix history in one month. I run a two-week tagging sprint:
Week 1 — inventory. Export all resources without Owner or Environment tags. Group by creator (CloudTrail userIdentity where available) and service.
Week 2 — policy. Enable tag policies or SCPs that require tags on create for new resources. Backfill tags on running resources that matter (compute, databases, buckets with data). Accept that some orphaned snapshots will sit until someone claims them.
I charge tagging sprints as explicit scope — it's not "part of migration" unless the SOW says so. Untagged accounts are a form of technical debt with a monthly interest payment.
Reserved Capacity: When I Bring It Up
Savings Plans and Reserved Instances are not month-one conversation for most startups. I mention them when:
- Steady-state compute spend exceeds roughly $2k/month for three consecutive months.
- Workloads are stable — same instance families, no imminent re-architecture.
- Finance wants predictability more than maximum flexibility.
I present options as a table: on-demand run rate, 1-year no-upfront SP, 3-year partial upfront, estimated savings, break-even month. I don't push 3-year commits on companies that might pivot — that's their risk call, not mine to optimize blindly.
For lab accounts and client dev environments, I recommend on-demand or spot with interruption tolerance. The savings from RI on a forgotten dev box are negative if you forget to rightsized first.
Handoff: Documenting Expected Run Rate
Every engagement ends with a runbook section I call expected monthly burn:
- Baseline $X at current traffic (link to Cost Explorer saved report)
- Drivers that scale with usage (NAT, data transfer, Lambda invocations)
- Thresholds that should trigger review (traffic 2× → expect ~Y)
Clients who've been burned before appreciate this more than another architecture diagram. Finance teams read dollars.
FAQ
How long does a monthly review take?
Roughly 2–3 hours for accounts under $10k/month once the process is mature; more in the first two months while tagging catches up.
Do you use third-party FinOps tools?
For mid-size accounts, native AWS tooling is enough. I add CloudHealth or similar only when the client already owns a license or has multi-cloud sprawl.
What if the client ignores recommendations?
I document the recommendation and savings estimate in writing. After two ignored cycles on the same issue, I stop re-reporting it unless spend crosses a new threshold — otherwise you become nagging, not advising.
Walkthrough: Anonymized Month-Over-Month Spike
A client SaaS account jumped from $4,200 to $6,100 month-over-month. Cost Explorer showed:
- +$1,100 — new GPU instance for an ML experiment left running in
dev(Owner tag missing). - +$480 — CloudWatch Logs ingestion from a verbose deploy pipeline logging full build artifacts.
- +$220 — cross-AZ traffic between services that had been colocated before a refactor.
Actions: terminated GPU after snapshot, dropped log verbosity in CI, documented AZ placement guideline in the runbook. Following month: $4,450 — slightly above original baseline because legitimate traffic grew, but spike removed.
That review took one afternoon. The alternative — finance panic, emergency call, blame — costs more than the afternoon.
Kubernetes and Container Cost Visibility
Container infrastructure introduces a layer of abstraction that standard Cost Explorer doesn't handle well. EC2 shows you instance hours; it doesn't show you which namespace or deployment drove them.
For EKS accounts, I add two tools to the review process:
Kubecost (or OpenCost) — deployed in-cluster, it allocates actual EC2, storage, and networking costs to namespaces, deployments, and labels. On a $15k/month EKS cluster, the difference between "EC2 costs jumped 30%" and "the analytics namespace is consuming 60% of compute" is the difference between a shrug and an action item.
AWS Container Insights — CloudWatch-native. Less granular than Kubecost but requires no additional software. Good enough for smaller accounts; I use it when clients resist adding another Helm release to their cluster.
Common container cost patterns I find in the monthly review:
- Overprovisioned resource requests —
requests.cpuset to 2000m on pods that actually peak at 200m means the node can't schedule more pods and you're paying for idle capacity. Check Kubernetes Vertical Pod Autoscaler recommendations before rightsizing. - Spot interruption fallback to on-demand — spot node groups with no fallback strategy run up an on-demand bill during spot scarcity periods. I add explicit on-demand fallback with instance diversification rather than letting Kubernetes scheduler fail to find nodes.
- Persistent volume claims in the wrong storage class — gp2 claims left on clusters that migrated to gp3 still pay gp2 prices. This is a one-time migration but it hides until you look at EBS in the CUR.
- Log egress from Fluent Bit or Fluentd — container log forwarders with verbose output generate significant CloudWatch Logs ingestion. Sampling in non-prod namespaces cuts this without data loss.
I add a Kubernetes-specific section to the monthly review table for any account running EKS. The format is the same (finding, service, estimated monthly $, owner, action) — the labels just come from Kubecost instead of Cost Explorer.
Multi-Region Cost Patterns
Remote clients often run multi-region architectures for latency or compliance without fully accounting for inter-region data transfer costs, which don't appear in the same line item as the compute that generates them.
Things I specifically check in multi-region accounts:
Cross-region replication on S3 — replication adds PUT request costs and data transfer out costs in addition to the storage. For large versioned buckets replicating to a secondary region, this can add 15–25% to the S3 line. I check whether the replication target is actually used for anything or was created for a DR plan that was never tested.
Aurora Global Database write forwarding — write forwarding through a secondary region adds latency and data transfer charges. If the secondary is there for read scaling only, global read routing without write forwarding is cheaper.
VPC Peering vs. Transit Gateway pricing — Transit Gateway charges per attachment and per GB processed. For two regions with low traffic, VPC peering is cheaper. I find accounts where Transit Gateway was deployed for simplicity but the traffic profile didn't justify it.
CloudFront vs. direct S3 or ALB access — CloudFront adds its own price per GB, but reduces origin data transfer charges. Whether it's net-positive depends on traffic patterns. I run a simple comparison: (CloudFront price/GB × GB) vs. (origin transfer price/GB × GB) + (CloudFront request charges). Many accounts have CloudFront deployed in front of private admin panels that could have stayed behind a direct endpoint.
The multi-region review takes longer because the data lives in different Cost Explorer filters. I run one pull per region combination, not one global report, and look at cross-region data transfer as a separate line item from compute.
Presenting Cost Data to Finance and Leadership
The hardest part of FinOps work is not the technical analysis — it's translating line items into decisions. Finance leaders don't think in GB of data transfer; they think in budget variance and cash flow.
My standard translation layer for client leadership reviews:
Replace service names with business outcomes. "EC2 costs rose 22%" means nothing without context. "The infrastructure supporting the checkout flow grew 22% month-over-month, tracking with the 18% order volume increase" is actionable — it tells them whether the growth is expected.
Show the trend, not just the snapshot. A single month's bill tells you where you are. Three months shows direction. Six months shows seasonality. I include a simple trend table (month, total, delta, whether within budget) in every leadership report. Leaders make better decisions when they see the cost trajectory, not just the current balance.
Separate growth from waste. A rising bill is not always a problem. If spend grew 15% and revenue grew 25%, that's efficiency improvement. If spend grew 15% and usage grew 5%, there's 10% waste to find. I always present these two numbers together: legitimate growth (correlated with usage) and waste (spend without matching usage).
Give one decision, not ten. Leadership reports with twelve action items get ten items ignored. I surface the single highest-value decision — usually the biggest waste item — and note the others in an appendix. The goal is to get one good decision per cycle, not overwhelm with a to-do list nobody will complete.
Include a next-month forecast. Based on current trajectory and any known changes (new service launching, contract ending, seasonal traffic), I add one sentence on expected next-month spend. Being consistently right about this builds credibility faster than any technical certification.
Sources
Related Articles
Kubernetes Cost Optimization: What I Actually Do on Production Clusters
11 min read
CareerForeign Income and Korean Tax: What Independent Contractors Working With US and EU Clients Need to Know
12 min read
CareerInfrastructure Documentation That Actually Gets Read: A Contractor's Approach
12 min read
Cloud Engineer · Part-time CS Lecturer · Seoul, South Korea · 5+ years infrastructure
I write about technical career management, variable income, and the performance habits that matter when you work independently — drawing on production infrastructure work and on teaching CS to students who won't accept hand-waving. Read full bio →
This article is for informational purposes only and does not constitute medical, legal, or financial advice.
Browse more articles