Read the bill by category first
Before changing anything, group your spend into five buckets and find the proportions:
- Compute — application servers, containers, functions.
- Data — managed databases, caches, search.
- Storage — object storage, snapshots, backups.
- Network — egress, load balancers, NAT gateways.
- Everything else — logging, monitoring, third-party managed services.
The common surprise: for pre-scale products, compute is often not the largest bucket. Network and "everything else" — particularly logging — frequently are. Teams optimise instance sizes for a week and miss a log pipeline costing more than the entire application tier.
The four line items that dominate
1. Idle non-production environments
Staging, QA, and demo environments provisioned like production and running continuously. A staging environment used during working hours costs roughly three times what it needs to. Scheduled shutdown outside business hours is the single highest-return change available to most teams, and it is close to risk-free.
2. Log ingestion and retention
Debug-level logging left on, retained for a year, indexed for full-text search. Logs are useful for days and archives are useful for compliance; paying for hot, searchable storage of everything in between is pure waste. Set retention deliberately, sample high-volume events, and keep debug level behind a flag.
3. Network egress and NAT
Egress charges and NAT gateway processing fees are invisible until they are not. Common causes: services in different availability zones chatting constantly, backups written across regions, and media served directly from object storage instead of through a CDN. A CDN in front of static assets often pays for itself immediately.
4. Oversized managed databases
Databases are provisioned for a launch that has not happened yet, then never revisited. Check actual CPU, memory, and connection use over 30 days. Early-stage databases are routinely three sizes larger than their workload, and unlike compute, nobody thinks to look.
What is safe to cut, and what is not
| Safe | Risky |
|---|---|
| Scheduling non-production environments off | Reducing production redundancy |
| Reducing log retention and verbosity | Turning off error tracking or alerting |
| Right-sizing based on 30-day metrics | Right-sizing based on a quiet week |
| Lifecycle rules on old snapshots | Shortening backup retention below recovery needs |
| Adding a CDN for static assets | Consolidating to one availability zone |
| Deleting genuinely unused resources | Long commitments before traffic is predictable |
The last risky row deserves emphasis. Reserved capacity and savings plans offer real discounts and lock you into a shape of usage you may not have in a year. For a product still finding its architecture, the flexibility is usually worth more than the discount.
Make cost visible before you optimise
One-off cleanups regress within two quarters unless something makes cost visible continuously. Three lightweight practices are enough:
- Tag by environment and service from the start. Without tags you cannot answer "what does staging cost", and every optimisation conversation begins with archaeology.
- A monthly five-minute review of spend by category, in a meeting that already exists. The goal is noticing a change while it is small.
- A budget alert at a threshold you would want to know about. Not to enforce anything — just so a surprise arrives as a notification rather than an invoice.
Cost per active user is the number worth tracking over time. Absolute spend rising with a growing product is fine; cost per user rising means something is structurally wrong, and it is a far better trigger for investigation than the total.
When to stop optimising
There is a point where this becomes a poor use of engineering time, and small teams often push past it.
A reasonable rule: if the remaining opportunity is smaller than a few days of engineering cost per year, stop. Spending a week to save a modest monthly amount is a bad trade for a team whose main risk is not shipping fast enough.
The exception is anything that scales with usage. A per-request inefficiency that is trivial at current volume becomes the whole bill at ten times the traffic. Fix things that grow with users; leave fixed overheads alone once they are reasonable.