How to Reduce Cloud Costs: The Four Line Items That Cut Half Our Bill
We went through our own Google Cloud bill line by line and removed close to half of it in three months. Almost all of it fell into four line items, and none of the cuts touched a feature.
Oct 29, 2026
To reduce cloud costs, measure seven days of real usage, then cut four line items: capacity sized for a peak that ended, per-unit meters nobody reads, database load that belongs elsewhere, and environments nobody turned off. That is where almost all of our own savings came from: close to half of a Google Cloud bill of about €1,900 (roughly $2,200) a month, removed in three months, with no feature changed. The numbers for each are below.
For months nobody felt that bill. A promotional credit was paying it.
That is worth saying first, because it is the most common way a cloud bill gets out of hand. A credit does not lower your spend. It hides it. On the day it runs out you meet a cost structure that nobody chose, built one reasonable decision at a time by people who never saw an invoice.
In July we went through the bill line by line. The two clusters run our own products and the client platforms we host, a transport marketplace among them, so the shape is the one a growing marketplace has: a production database, a dozen services, a staging copy of most of them, CI, and a few managed extras. Over three months we removed close to half of the gross spend. No feature changed.
Almost every euro came out of four line items. They are below, with the numbers, followed by what to measure before you touch anything and the cuts we deliberately did not make.
Why the bill grows faster than revenue
Revenue on a marketplace grows with transactions. The bill grows with decisions.
Every new service gets a CPU and memory reservation, picked on the day it shipped, usually generously because an out-of-memory crash in week one is embarrassing. Every managed feature a console offers gets switched on because it is one click and looks responsible. Every environment gets created for a reason that is real at the time. None of these is ever revisited, because nothing forces a revisit: the platform keeps working, and the bill arrives at an address that is not the engineering team's.
So the bill ratchets. It goes up when someone adds something and never comes down by itself. The four line items below are the four places that ratchet catches.
Line item one: capacity sized for a peak that ended
Our production database ran on 4 vCPU and 32 GB of memory. Over seven days it used 2 to 3% of its CPU, with a peak of 8 concurrent connections. We halved it to 2 vCPU and 16 GB and saved about €180 a month.
Two details made that safe rather than lucky. Memory was the real constraint: it sat at around 12.8 GB, which is 80% of the smaller tier, so we kept the tier that adds a data cache and watched memory after the change. And resizing a managed database restarts it, so the change waited for a maintenance window instead of shipping the afternoon it was approved.
The same pattern sits one level down, inside Kubernetes, and it is less visible. A cluster does not size itself on what pods use. It sizes itself on what they reserve. Some of our services reserved 500 millicores of CPU and used 11. The result was production nodes that looked two-thirds full to the scheduler while doing almost nothing, which means the autoscaler will never remove one. Trimming the reservations, and only the reservations, not the limits, freed about 775 millicores in production with no risk of a crash.
Right-sizing goes both ways, though. One of our services had a memory request of 512 Mi against a median usage of 697 Mi. It ran permanently over its own reservation, which made it the first thing the node evicted under pressure. We raised it to the 95th percentile. The goal is to make the numbers match reality, not to make them smaller.
What to look for: any database or node pool whose seven-day peak is under a quarter of its size, and any service whose reservation is ten times its usage, or below it.
Line item two: meters nobody reads
The most expensive surprises were not big machines. They were services billed per unit, quietly producing output that nothing consumed.
- Container vulnerability scanning: about €169 a month. It scanned every image we pushed. Nothing enforced its results and nobody read them. Binary Authorization, the feature that would have acted on it, had never been enabled. We turned the scanning off.
- Default Prometheus metric packages: about €48 a month. Both clusters had the full set of kube-state, cAdvisor, kubelet and GPU metric packages switched on, because that is the default. Every sample is billed. Our alerts read a different set of metrics entirely.
- An AI code reviewer: most of a €400 a month line. It regenerated every code suggestion on every push to a pull request. We kept the full review when a pull request opens and made the expensive pass available on demand. Review depth did not change.
- The container registry: about €75 a month across two passes. Repositories had grown past 100 GB with no retention policy at all, because every CI build pushes an image. By the second pass the registry held around 580 GB. We now keep the last ten builds for rollback and expire the rest.
On a marketplace, add data egress to this list. Image delivery to buyers, traffic between availability zones, and calls between services in different regions are all billed per gigabyte, and almost nobody estimates them at design time. It was not a top line on our bill. On an image-heavy platform it can be.
What to look for: export your billing data and sort by SKU rather than by service. Services hide meters inside them. The surprises live in the long tail, in lines of €30 to €200 that nobody would approve if asked.
Line item three: the database doing work that belongs elsewhere
Two internal reporting dashboards had no human users left. We checked: no sessions on either. Their background schema sync and query logging still made up about 19% of the main database's transactions and 17% of its connections.
Retiring them saved roughly €3 a month in direct cost. The point was never those euros. It was a quieter database before we resized it. If we had sized the instance on its observed load, we would have bought capacity for a fifth of the work that served nobody.
That is the pattern on growing marketplaces too, with different tenants. Search queries on the primary database. Reporting and exports running against production at month-end. Job queues and session storage that started in Postgres because it was there. Each makes the database look busier than the product needs, and the usual response is a bigger tier, paid every month, for load that should have lived somewhere cheaper.
What to look for: a breakdown of transactions and connections per database and per client, not just the instance's total CPU. The question is not "is the database busy" but "busy with what".
Line item four: environments that were never turned off
Moving the staging node pool to Spot VMs saved about €120 a month. Spot capacity can be reclaimed at short notice, which staging tolerates and production does not. That one distinction is most of the decision.
Two smaller findings matter more for the habit than for the euros. A product that was no longer in active development still had its full staging copy deployed, and those three services were the only thing keeping a second staging node alive. And before the retention policy above, the registry had simply never been asked to forget anything.
What to look for: every environment and every deployment in it, with the date someone last used it. Preview environments created per pull request and never torn down are the marketplace version of this.
What to measure before changing anything
Every cut above started from a measurement, not from a guess. These four steps are what made the rest safe.
- Turn on cost allocation per workload. On GKE it is a cluster setting, and it attributes spend per namespace and workload in the billing export. Without it, a cluster is one opaque line.
- Export billing to BigQuery. The console's charts answer the questions the console thought of. The export lets you sort by SKU, which is how line item two was found.
- Set a budget with alerts at 50, 90 and 100% of last month's spend, plus one at 90% of the forecast. The forecast alert is the one that matters: it fires while the month can still be saved, not after.
- Pull seven days of real usage per service, CPU and memory, median and 95th percentile. Size reservations on the 95th percentile, not on the median and not on a comment someone left in the config two years ago. One of ours claimed an average of 430 millicores for a service whose real peak was 12.
One caveat on labels: if you plan to split the bill between staging and production with an environment label, check what it misses. On our project the label undercounted the VMs that GKE manages itself, so one budget for the whole project was more honest than two that looked precise.
What not to do
The cuts we chose not to make taught as much as the ones we made.
Do not delete what you cannot restore. When we retired the two dashboards, we removed the deployments but kept their databases and secrets. Reverting one commit brings them straight back. Dropping the databases is a separate change, made after a verified backup, because a deleted database has no undo.
Do not cut below the line where services crash. We could have collapsed staging onto a single node by squeezing two services under their known-safe reservations. Those services had a documented crash history at lower values, so staging stays on two nodes. One node's worth of savings is not worth a staging environment that falls over during a demo.
Do not put production on Spot to match staging. The savings are real and the reclaim notice is short. Production pays on-demand prices for a reason.
Do not spend engineering weeks to save €20 a month. Most of what we cut was configuration: a tier, a reservation, a toggle, a retention rule. The optimisations that require rewriting a service are sometimes right, but they belong in a roadmap with an engineering cost attached, not in a cost review. If a change costs more in salary than it saves in a year, it is not a saving.
Most cloud savings are not clever. They are a list of things that were right once and never revisited, and the work is making that list and putting a number next to each line.
If your platform's bill is growing faster than its revenue, cloud cost is one of the workstreams in our technical due diligence and scale readiness sprint: measured usage, a costed list of changes, and the order to make them in. If the deeper problem is that nobody senior owns these decisions month to month, that is a fractional CTO question.
Read next: what a small company should do about the next AWS outage, disaster recovery after the AWS UAE outage, and cost efficiency for LLMs in production.