StackPractices
intermediate By Mathias Paulenko

Cloud Cost Optimization: A Complete Program Guide

A practical guide to cloud cost optimization: right-sizing, reserved capacity, spot instances, tagging and FinOps practices that reduce spend.

Overview

Cloud cost optimization is the practice of reducing infrastructure spending without degrading the performance or reliability your users depend on. It mixes technical levers (instance types, storage classes, purchasing models) with organizational ones (tagging, chargeback, cost-aware engineering culture).

This guide is the program: a seven-step process you run continuously, plus a worked example with real numbers. If you want the copy-paste commands and scripts first, the cost optimization recipe has the quick wins. If you’re AWS-only, the AWS cost optimization guide goes deeper on that provider. For the organizational discipline (maturity model, team KPIs, exec reporting) see the FinOps guide.

A realistic expectation for a first pass: 20–40% off the bill. I’ve seen teams hit the top of that range just by scheduling dev environments and deleting orphaned storage, before touching a single reserved instance.

flowchart diagram: Rising cloud bill

When to Use

Reach for this guide when:

  • Your cloud bill grows faster than your user base or revenue
  • You’ve got unused or underutilized resources running 24/7
  • Nobody can attribute spend to a team; every invoice review turns into archaeology
  • You’re preparing for an infrastructure budget review or audit
  • You’re migrating workloads and want to optimize from day one instead of inheriting waste

Core Concepts

Six levers produce nearly all savings. Understand these before touching anything:

ConceptWhat it meansTypical lever
Right-sizingMatch resource capacity to measured usageDownsize instances under ~40% CPU
Reserved capacityPre-purchased commitment at a discount1–3 year RIs, Savings Plans
Spot/preemptibleSpare capacity sold at steep discountsBatch, CI agents, stateless services
Storage tieringMove cold data to cheaper classesS3 IA, Glacier, lifecycle policies
TaggingLabel resources with owner, team, environmentMakes chargeback and accountability possible
FinOpsThe operating model that makes it stickBudgets, reviews, shared KPIs

The first five are technical: you can execute them this week. The last one is what keeps savings from evaporating next quarter.

The Seven-Step Program

1. Get Visibility Into Current Spend

You can’t optimize what you can’t attribute. Start by answering three questions: which services cost the most, which environments drive them, and which teams own them.

# AWS Cost Explorer: spend grouped by service
aws ce get-cost-and-usage \
  --time-period Start=2026-01-01,End=2026-06-01 \
  --granularity MONTHLY \
  --metrics BlendedCost \
  --group-by Type=DIMENSION,Key=SERVICE

# Azure equivalent
az costmanagement query \
  --type ActualCost \
  --timeframe MonthToDate \
  --dataset-granularity Daily

The reports that actually matter:

  • Spend by service: usually 2–3 services cover 80% of the bill (compute, storage, data transfer)
  • Spend by environment: non-production is almost always 30–50% of total spend
  • Spend by team or cost center: impossible without tags, which is why step 6 exists
  • Idle resource report: unattached volumes, unassociated IPs, forgotten load balancers

For serverless workloads, pair this review with an upfront estimate: the Serverless Cost Estimation Template prices a workload before deployment, so Cost Explorer reports have a baseline to reconcile against.

Two things the reports won’t tell you directly: whether the spend is useful, and who should own it. A $5,000 data pipeline bill can be perfectly justified or completely wasted depending on whether anyone reads the output. That judgment call needs the team that built the workload, which is exactly why step 6 (tagging and chargeback) is a prerequisite for sustained optimization rather than an optional extra.

2. Right-Size Compute

Oversized instances are the most common waste pattern. The trap: teams look at average CPU, see 15%, and conclude the instance is idle. Averages lie: a batch worker that spikes to 90% for an hour and idles the rest still needs the headroom. Look at p95 utilization over 30 days, not the mean.

# Flag underutilized instances using p95, not average
import pandas as pd

df = pd.read_csv('instance_metrics.csv')  # 30 days of CloudWatch/Azure Monitor data

# p95 under 40% CPU over 30 days = downsizing candidate
candidates = df[df['cpu_p95'] < 40]

for _, row in candidates.iterrows():
    print(f"{row['instance_id']} ({row['instance_type']}): "
          f"p95 CPU {row['cpu_p95']:.1f}% — downsize or terminate")

Right-sizing guidelines that hold up in practice:

  • Review monthly; target 40–70% sustained utilization, not a fixed number
  • Burstable families (AWS T-series, Azure B-series) fit spiky workloads because they bank CPU credits during idle periods
  • Dev and staging can be downsized aggressively; nobody notices 2 vCPU vs 4 on a test box
  • ARM instances (AWS Graviton, Azure Ampere) deliver 20–40% better price-performance for most Linux workloads, but verify your dependencies compile for ARM first

3. Buy Reserved Capacity Strategically

Once right-sizing stabilizes your baseline, commit to it. Buying reservations before right-sizing locks in the waste you were trying to remove.

Purchase typeDiscountFlexibilityBest for
Standard RIUp to 72%Low (specific region + family)Stable production baselines
Convertible RIUp to 54%Medium (change family)Predictable but evolving loads
Savings PlansUp to 72%High (any usage in scope)Mixed, flexible workloads
# Break-even math (real numbers)
# On-demand: $0.192/hour ≈ $140/month
# 1-year RI, all upfront: ≈ $85/month
# 1-year RI, partial upfront: ≈ $90/month
# Break-even: 7–8 months — don't buy if the workload dies sooner

Rules that prevent expensive mistakes:

  • Only reserve capacity running >70% of the time; an RI on an idle box is a guaranteed loss
  • Start with partial upfront to preserve cash flow; all-upfront saves ~5% more
  • Prefer Savings Plans when the workload may change shape (right-sizing again, ARM migration, region moves)
  • Review reserved coverage quarterly: under 70% coverage means wasted on-demand spend, under 90% utilization means stranded commitment

4. Use Spot and Preemptible Capacity

Interruptible capacity trades reliability for 60–90% discounts. The catch: the cloud can reclaim it with ~2 minutes notice (AWS) or ~30 seconds (GCP).

# Karpenter NodePool requesting spot capacity
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: spot-workloads
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
      nodeClassRef:
        name: default
  limits:
    cpu: 1000
    memory: 1000Gi

Workloads that tolerate interruption: batch processing, CI build agents, stateless services behind a load balancer, ML training with checkpointing, dev/test. Workloads that don’t: databases, queues, anything with in-memory state, singleton services.

Design for eviction, don’t hope it won’t happen: spread across 5+ instance families and 2–3 AZs (capacity pools are per-family-per-AZ, so diversity is what actually reduces interruption frequency), keep an on-demand baseline for critical replicas, and handle the interruption notice properly. On AWS the notice arrives as a two-minute warning via instance metadata and EventBridge; Kubernetes autoscalers and managed node groups translate it into a drain. A workload that checkpoints progress (or is idempotent enough to simply retry) treats eviction as a slowdown, not a failure. A workload that dies mid-job and loses state turns the 70% discount into a reliability incident.

5. Optimize Storage and Data Transfer

Storage grows silently: it’s the line item nobody reviews until it’s the second-largest on the bill.

StrategyWhat it doesTypical savings
TieringCold data → cheaper classes50–80% on cold objects
CompressionSmaller payloads stored60–90% on logs/backups
DeduplicationNo redundant copies30–50% on backups
Lifecycle policiesAuto-transition/delete by ageSet-and-forget
Volume right-sizingShrink over-provisioned disks20–40%
// S3 lifecycle: archive logs to cheaper tiers automatically
{
  "Rules": [{
    "ID": "LogArchive",
    "Status": "Enabled",
    "Filter": { "Prefix": "logs/" },
    "Transitions": [
      { "Days": 30, "StorageClass": "STANDARD_IA" },
      { "Days": 90, "StorageClass": "GLACIER" }
    ],
    "Expiration": { "Days": 365 }
  }]
}

Don’t forget data transfer: cross-AZ traffic (~$0.01/GB each way), cross-region ($0.02–0.09/GB), and NAT Gateway processing fees often exceed the compute costs of chatty microservices. Co-locating in the same AZ and using VPC endpoints for S3/DynamoDB removes entire line items.

6. Enforce Tagging and Chargeback

Visibility without ownership decays. Tags are how “the bill” becomes “team X’s bill”, and behavior changes fast when spend has a name attached.

Required tags to enforce:

  • Environment: production, staging, development
  • CostCenter: team or department that pays
  • Owner: human contact for the resource
  • Project: application or project it belongs to
  • AutoShutdown: yes/no for non-production resources
# Find untagged resources before finance does
aws resourcegroupstaggingapi get-resources \
  --resources-per-page 100 | \
  jq '.ResourceTagMappingList[] | select(.Tags | length == 0) | .ResourceARN'

# Verify a tagging policy actually propagated
aws resourcegroupstaggingapi get-resources \
  --tag-filters Key=Environment,Values=development \
  --resource-type-filters ec2:instance

Enforcement beats reminders: block tagless resource creation with SCPs in AWS Organizations (or Azure Policy), and make tags mandatory in Terraform modules: a required_tags variable with no default fails the plan before the resource ever exists. Retrofitting tags onto existing resources is the tedious part; bulk-tag via the Resource Groups Tagging API and expect the first pass to miss edge cases like resources created manually in the console.

Once tags are enforced, the Cloud Cost Allocation Template turns them into per-team numbers: split rules for shared costs, a monthly report format, and dispute handling.

7. Monitor Continuously, Alert Early

Cost optimization isn’t a quarterly project; it’s a monitoring problem. The goal is catching drift while it’s a $50 anomaly, not a $5,000 invoice.

AlertThresholdResponse
Daily spend anomaly>20% above 30-day averageInvestigate same day
Budget threshold80% of monthly budgetNotify owning team
Resource sprawlNew resources >$500/dayRequire approval
Idle resources<5% CPU for 7 daysAuto-tag for review

Native anomaly detection (AWS Cost Anomaly Detection, GCP and Azure equivalents) is worth enabling since it catches the “someone left a p3.16xlarge running over the weekend” class of incident that threshold alerts miss. Pair it with a lightweight review cadence: weekly glance at top movers, monthly per-team report, quarterly reserved-capacity audit.

Worked Example: Startup Cuts $2,800/Month (35%)

A five-service startup on AWS burning $8K/month, told to cut 40% without user impact.

Phase 1, inventory: the spend distribution before touching anything.

ServiceCost/month% of totalTagged
EC2 (8 instances)$3,20040%Partial
RDS PostgreSQL$1,50019%Yes
ElastiCache$80010%No
S3 (10TB)$6008%Yes
ALB + NAT$5006%No
Lambda$4005%Yes
Other$1,00012%No

Phase 2, prioritized actions (impact order, not effort order):

  1. Right-size EC2: 4 instances under 20% p95 CPU downsized, 2 under 10% terminated → $1,200/month
  2. Reserved Instances: 3 always-on instances to 1-year RI at ~40% off → $600/month
  3. S3 lifecycle: objects >30d to IA, >90d to Glacier → $300/month
  4. Spot for batch: 2 processing workers to spot at ~70% off → $400/month
  5. NAT optimization: S3 access moved to VPC endpoint → $200/month
  6. Lambda memory: 1GB → 512MB after measuring duration → $100/month

Phase 3, total: $2,800/month (35%). Missed the 40% target, achieved with zero user-facing changes.

Phase 4, governance so it sticks: mandatory tags (team, env, project, cost-center), budget alerts at 80% and 100%, weekly per-team cost report, quarterly RI review.

Lessons that generalize: visibility comes first (half the savings were invisible before the inventory), right-sizing is the fastest win, RI only for proven always-on workloads, lifecycle policies are set-and-forget, and governance is what prevents the creep back.

Just as instructive is what they didn’t touch: ElastiCache ($800) was left alone because the cache sat on the hot path of their most-used endpoint; optimizing it would have been a performance risk for maybe $150/month. The RDS bill was already a reserved instance and properly sized. Good cost optimization is also knowing which line items are already fine; chasing the last 10% of a $500 line while ignoring a $3,000 one is how programs lose credibility.

A Realistic 90-Day Rollout

Don’t try to run all seven steps in week one. The sequence matters because each step feeds the next:

  • Weeks 1–2: visibility and tags. Turn on cost reports, deploy the tagging policy, run the idle-resource sweep. Output: a baseline and a per-service breakdown.
  • Weeks 3–4: quick wins. Right-size the obvious candidates, schedule dev environments, delete confirmed orphans. This is usually 15–25% on its own and it funds the program’s credibility.
  • Month 2: commitments and spot. With a clean baseline, buy Savings Plans for the stable floor and migrate suitable workloads to spot.
  • Month 3: governance. Budgets and anomaly alerts live, per-team reports going out, quarterly reserved-capacity review on the calendar.

Skipping the baseline and jumping straight to commitments is the classic failure mode: you end up with a Savings Plan sized for the waste you were about to remove.

Expect diminishing returns after the first cycle. The initial pass harvests the easy waste; the second finds smaller, harder items like data-transfer re-architecture or unit-cost per request. That’s normal. What keeps the program alive isn’t another heroic sweep but the boring quarterly loop: re-baseline, adjust commitment coverage, re-run the idle scan, and report the trend line to whoever owns the budget.

What Works

  • Optimize continuously, not annually. Monthly reviews catch drift; annual ones find it.
  • Attack the biggest spend first. Compute, then storage, then networking. The Pareto order holds.
  • Involve engineering early. Most savings require code or architecture changes, not just billing tweaks.
  • Measure actual savings. Track realized reductions against the baseline, not projected recommendations.
  • Never trade reliability for marginal savings. A 10% saving that doubles your incident rate is expensive.
  • Automate enforcement. Policy-as-code for tags, shutdowns, and lifecycle rules beats process documents nobody reads.

Common Mistakes

  • Reserving before right-sizing. RIs only save on capacity you actually keep; commit after the cleanup, not before.
  • Ignoring data transfer. Cross-AZ, egress, and NAT fees regularly exceed the compute costs that get all the attention.
  • Over-provisioning storage. Volumes get created at max size and never shrink; EBS bills grow forever.
  • Neglecting dev environments. Dev/test routinely consumes 30–50% of spend while production gets all the scrutiny.
  • Optimizing without a baseline. If you can’t show before/after numbers, the savings are unverifiable and the program loses sponsorship.
  • One-shot cleanups. Deleting idle resources once, without a review loop, guarantees the waste returns.

Variants by Provider

  • AWS: Savings Plans, Graviton instances, S3 Intelligent-Tiering, Cost Anomaly Detection, spot with Karpenter
  • Azure: Hybrid Benefit (reuse Windows/SQL licenses), Reserved VM Instances, Spot VMs, Azure Advisor recommendations
  • GCP: Committed Use Discounts, Preemptible/Spot VMs, Sustained Use Discounts (automatic: you get them for free)
  • Multi-cloud: compare unit prices per workload type; egress pricing differs enough to change architecture decisions

Conclusion

Cloud cost optimization is a discipline, not a project. The seven steps here (visibility, right-sizing, commitments, spot, storage, tagging, monitoring) run as a loop, not a checklist. Start with the inventory, take the fast wins (right-size, schedule, delete orphans), then build the governance that keeps the savings.

The honest measure of a working program isn’t a single reduced invoice; it’s that the next invoice is also lower, and the one after that. Teams that get there treat cost the way they treat reliability: a metric with an owner, a review cadence, and alerts when it drifts. Everything in this guide is in service of that operating rhythm.

See Also

Frequently Asked Questions

How much can I realistically save?

A first pass typically finds 20–40%: scheduling non-production (~50–70% off those environments), right-sizing (15–30% of compute), and orphaned resources (whatever's been leaking). Mature FinOps organizations sustain 50%+ by layering commitments on top.

Should I use spot instances for production?

Only for stateless, fault-tolerant workloads with a real on-demand fallback, never databases or singletons. The standard pattern is a mixed group: on-demand baseline for availability, spot for burst capacity.

What's the difference between a Reserved Instance and a Savings Plan?

Both trade commitment for discount. RIs lock a specific instance type (sometimes a specific AZ) for 1–3 years; Savings Plans commit to a dollars-per-hour spend that applies across instance families and regions. Savings Plans are the safer default for most teams.

How do I convince leadership to invest in cost optimization?

Don't pitch "savings"; show current waste. An inventory of idle resources plus the projected annual total converts better than any framework deck. The worked example above is the template.

What's the difference between FinOps and cost optimization?

Cost optimization is the technical work: right-sizing, purchasing, lifecycle policies. FinOps is the operating model around it: who owns spend, how it's reported, which KPIs teams share. The FinOps guide covers that discipline in depth.

How do I allocate costs to teams when resources aren't tagged?

Use heuristics until tags land: split shared costs by service count or by CPU/memory usage per team. On Kubernetes, Kubecost allocates per namespace; on RDS, split by query volume per schema. Then enforce tags via CI/CD: SCPs in AWS Organizations can block resource creation without them.