Reduce costs¶
Most teams can cut their GCP costs with a few changes to their nais.yaml manifests. The largest savings come from right-sizing database instances, adjusting resource requests, and tuning replica counts.
Find what to optimize¶
Before making changes, check your team's current resource usage and costs in Nais Console :
- Cost — daily costs broken down by GCP service. Find it under your team's Cost tab.
- Utilization — CPU and memory requested vs. actually used, per workload. Find it under your team's Utilization tab.
The Utilization page shows how much you request and how much you use. The gap between these numbers is what you're overpaying for.
Resource requests¶
Lower CPU requests¶
Kubernetes lets pods burst beyond their CPU request when the node has spare capacity. By removing the CPU limit and setting the request to the application's idle baseline, you pay for less without losing burst capacity.
Set the CPU request to the idle baseline (what your app uses during nights and weekends):
Typical values:
| Application type | CPU request (prod) | CPU request (dev) |
|---|---|---|
| Frontend | 20m | 10m |
| Backend (Ktor, Spring Boot) | 30m | 10m |
Info
Don't set a CPU limit. Removing it lets the application burst freely when it needs more CPU, without paying for reserved capacity it rarely uses.
Warning
Don't go below your application's actual baseline. When nodes are busy, each pod only gets CPU proportional to its request. Too-low requests cause slow responses, failed health checks, and pod restarts. They also make the autoscaler flap, since even minor CPU usage exceeds the 50% scaling threshold.
Lower memory requests¶
Memory requests reserve capacity on the node, just like CPU. If your application requests 512 Mi but uses 200 Mi, the remaining 312 Mi is wasted — no other pod can use it.
Check actual memory usage in the Utilization tab in Nais Console , then set the request to match the observed peak with some headroom:
Warning
If a pod exceeds its memory request and the node is under pressure, it may be evicted. Leave some margin above the observed peak.
Replicas¶
Reduce minimum replicas in dev¶
The default minimum is 2 replicas. Dev environments that don't need high availability can run with 1:
This halves the base cost for that workload in dev.
Lower maximum replicas¶
The default maximum is 4 replicas. If your application handles its load with fewer, lower the max to prevent unnecessary scaling:
See the automatic scaling reference for details on scaling thresholds and strategies.
Database tier¶
Database instances are often the largest line item on the Cost page.
Use a smaller tier in dev¶
Dev databases rarely need production-grade hardware. Switch to the smallest available tier:
Warning
db-f1-micro does not have an SLA. This is fine for dev, but not for production.
Right-size your production database¶
Check your database's CPU usage in the Google Cloud Console or through database observability tools.
If CPU usage stays low, lower the tier. The smallest tier with an SLA is db-custom-1-3840:
See the full list of available tiers.
Warning
Changing the tier restarts the database, causing a few minutes of downtime.
Fix slow queries instead of scaling up¶
High CPU usage often comes from missing indexes or expensive queries, not actual load. Before upgrading the tier, check for these using Query Insights in the Google Cloud Console.
Evaluate high availability settings¶
If your database has highAvailability: true, consider whether pointInTimeRecovery: true gives you sufficient protection instead. High availability runs a standby instance that doubles the instance cost .
spec:
gcp:
sqlInstances:
- type: POSTGRES_17
tier: db-custom-1-3840
pointInTimeRecovery: true
databases:
- name: mydb
Database disk¶
Cloud SQL charges for allocated disk space , not used space.
Cap automatic disk growth¶
When diskAutoresize is enabled, GCP increases storage automatically when disk usage is high. The disk never shrinks back. Without a limit, this can grow unchecked.
Set diskAutoresizeLimit to cap the maximum size in GB:
spec:
gcp:
sqlInstances:
- type: POSTGRES_17
tier: db-custom-1-3840
diskAutoresize: true
diskAutoresizeLimit: 50
databases:
- name: mydb
The default limit is 0 (unlimited). Set it to a value that fits your expected data growth.
See the diskAutoresize and diskAutoresizeLimit reference.
Use HDD for low-I/O databases¶
The default disk type is SSD. If your database has low I/O requirements (infrequent reads and writes), HDD is cheaper:
spec:
gcp:
sqlInstances:
- type: POSTGRES_17
tier: db-custom-1-3840
diskType: HDD
databases:
- name: mydb
Info
SSD is the right choice for most production databases. Use HDD only for archival or low-traffic databases.
Reclaim unused disk space¶
If your database has far more disk allocated than it actually uses, the only way to reclaim the space is a database migration. Cloud SQL does not support shrinking disk.
Advanced: database migration¶
These changes save the most but require a database migration.
Migrate to the Nais cluster¶
Databases outside the Nais cluster require a Cloud SQL Proxy sidecar, which adds overhead and cost. Migrating the database into the cluster removes this.
Tip
If you haven't migrated databases before, ask someone with experience first. Instances with non-default settings can be tricky to migrate.
Monitor your costs¶
Use the Cost and Utilization tabs in Nais Console to track the effect of your changes over time.