GCP gives you something AWS and Azure don't: custom machine types. Instead of picking from a fixed menu of predefined VMs, you can specify exact vCPU and memory to match your workload. Your app needs 2 vCPUs and 5 GB RAM? Don't pay for the 16 GB that comes with e2-standard-4. Create an e2-custom-2-5120 and stop overpaying.
Here's the problem: teams pick "standard" machine types during initial setup, and nobody ever changes them. That e2-standard-4 with 4 vCPUs and 16 GB of RAM? Your monitoring shows it uses 12% CPU and 4 GB of memory. You're paying for 3.5 vCPUs and 12 GB of RAM that sit idle every single day.
Studies show that 78% of organizations waste 21-50% of their cloud compute spend on oversized VMs. GCP's Recommender API identifies exactly which VMs are oversized, and custom machine types let you right-size them to the exact resources needed.
Let's fix this with Terraform.
📊 The Cost of Over-Provisioning
Here's what you're paying vs what you could be paying for a typical web API server:
| Machine Type | vCPUs | Memory | Monthly Cost | CPU Used | RAM Used |
|---|---|---|---|---|---|
| e2-standard-4 (current) | 4 | 16 GB | ~$97 | 12% | 4 GB |
| e2-standard-2 (downsize) | 2 | 8 GB | ~$49 | 24% | 4 GB |
| e2-custom-2-5120 (right-size) | 2 | 5 GB | ~$42 | 24% | 4 GB |
That's a 57% savings on a single VM. Multiply that across 20 staging VMs and you're saving $1,100/month.
The custom machine type costs less than the predefined e2-standard-2 because you're not paying for 3 GB of RAM you don't use. Yes, custom types carry a 5% premium over predefined pricing per vCPU/GB, but when you're cutting resources by 50-70%, the net savings dwarf the premium.
🔍 Step 1: Find Oversized VMs with the Recommender API
GCP automatically monitors your VM utilization and generates right-sizing recommendations after 8 days of data. Here's how to pull those recommendations with gcloud:
# List all VM right-sizing recommendations for a project
gcloud recommender recommendations list \
--project=YOUR_PROJECT_ID \
--location=us-central1-a \
--recommender=google.compute.instance.MachineTypeRecommender \
--format="table(
content.overview.resource,
content.overview.currentMachineType.name,
content.overview.recommendedMachineType.name,
content.overview.costProjection.cost.units
)"
This outputs something like:
RESOURCE CURRENT RECOMMENDED SAVINGS
staging-api-1 e2-standard-4 e2-custom-2-5120 -$55/month
staging-worker-3 e2-standard-8 e2-standard-2 -$145/month
dev-ml-notebook n1-standard-16 n1-standard-4 -$290/month
⚠️ Key detail: Recommendations require at least 8 days of data. New VMs won't have recommendations yet. Also, recommendations are NOT generated for VMs in managed instance groups (MIGs), App Engine, Dataflow, GKE, or Dataproc - those are managed differently.
🔧 Step 2: Custom Machine Types in Terraform
The syntax is simple. Instead of e2-standard-4, use e2-custom-{vCPUs}-{memoryMB}:
# BEFORE: Paying for 16 GB when you use 4 GB
resource "google_compute_instance" "api_server" {
name = "staging-api"
machine_type = "e2-standard-4" # 4 vCPU, 16 GB - $97/mo
zone = var.zone
# ...
}
# AFTER: Pay for exactly what you need
resource "google_compute_instance" "api_server" {
name = "staging-api"
machine_type = "e2-custom-2-5120" # 2 vCPU, 5 GB - $42/mo 👈 57% saved
zone = var.zone
# ...
}
Custom machine type naming format:
{series}-custom-{vCPUs}-{memoryMB}
Examples:
e2-custom-2-5120 = E2 series, 2 vCPUs, 5 GB RAM
n2-custom-4-10240 = N2 series, 4 vCPUs, 10 GB RAM
e2-custom-6-12288 = E2 series, 6 vCPUs, 12 GB RAM
Rules for custom machine types:
- Memory must be a multiple of 256 MB
- E2 series: 0.5 to 8 GB per vCPU
- N2/N2D series: 0.5 to 8 GB per vCPU (up to 6.5 GB without extended memory)
- vCPUs must be 1, or an even number (2, 4, 6, 8...)
- Available on E2, N1, N2, N2D, N4, N4D, N4A series
🏗️ Step 3: Right-Sizing Module with Environment-Aware Machine Types
Different environments need different sizes. Here's a module pattern that makes right-sizing automatic:
variable "environment" {
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
locals {
# Right-sized machine types per environment
machine_types = {
web_api = {
dev = "e2-custom-2-4096" # 2 vCPU, 4 GB - minimal for dev
staging = "e2-custom-2-5120" # 2 vCPU, 5 GB - matches actual usage
prod = "e2-custom-4-8192" # 4 vCPU, 8 GB - headroom for traffic
}
worker = {
dev = "e2-custom-2-4096" # 2 vCPU, 4 GB
staging = "e2-custom-4-8192" # 4 vCPU, 8 GB
prod = "e2-custom-8-16384" # 8 vCPU, 16 GB
}
database = {
dev = "e2-custom-2-8192" # 2 vCPU, 8 GB - DB needs more RAM
staging = "e2-custom-2-10240" # 2 vCPU, 10 GB
prod = "n2-custom-4-32768" # 4 vCPU, 32 GB - prod DB gets N2
}
}
}
resource "google_compute_instance" "api" {
name = "${var.app_name}-api-${var.environment}"
machine_type = local.machine_types["web_api"][var.environment]
zone = var.zone
boot_disk {
initialize_params {
image = "debian-cloud/debian-12"
}
}
network_interface {
network = var.network_id
subnetwork = var.subnet_id
}
labels = merge(local.common_labels, {
role = "api"
rightsized = "true"
rightsized-date = "2026-02"
})
}
⚠️ Gotcha: Changing
machine_typeon an existing VM requires a stop and restart. Terraform will do this automatically, but plan for a brief downtime window. For production, use a rolling update strategy with MIGs or do it during maintenance windows.
📋 Step 4: The Right-Sizing Decision Matrix
Not every recommendation should be blindly applied. Here's how to evaluate:
| Scenario | Action | Why |
|---|---|---|
| CPU < 20% for 30+ days | Downsize vCPUs | Clearly over-provisioned |
| RAM usage < 50% consistently | Switch to custom with less RAM | Pay only for what you use |
| CPU spikes to 90% briefly, then idle | Keep current size OR use autoscaling | Spikes need headroom |
| Dev/staging VM matches prod spec | Downsize aggressively | Non-prod doesn't need prod resources |
| Recommendation says upgrade | Actually upgrade | Under-provisioned hurts performance |
The #1 rule: right-size non-production first. Dev and staging VMs are almost always massively over-provisioned because they were copied from production configs. Start there for quick, safe wins.
🔄 Step 5: Automate Recommendation Checks
Set up a scheduled Cloud Function that pulls recommendations and posts them to Slack so your team reviews them regularly:
# Cloud Scheduler triggers weekly right-sizing review
resource "google_cloud_scheduler_job" "rightsizing_check" {
name = "weekly-rightsizing-check"
schedule = "0 9 * * MON" # Every Monday at 9 AM
time_zone = "America/Los_Angeles"
http_target {
http_method = "POST"
uri = google_cloudfunctions2_function.rightsizing_reporter.url
oidc_token {
service_account_email = var.scheduler_sa_email
}
}
}
The Cloud Function fetches recommendations via the Recommender API and posts a summary to Slack:
# Simplified example - Cloud Function code
from google.cloud import recommender_v1
def get_rightsizing_recommendations(project_id, zone):
client = recommender_v1.RecommenderClient()
parent = (
f"projects/{project_id}/locations/{zone}"
f"/recommenders/google.compute.instance.MachineTypeRecommender"
)
recommendations = client.list_recommendations(parent=parent)
savings = []
for rec in recommendations:
overview = rec.content.overview
savings.append({
"vm": overview.get("resource", ""),
"current": overview.get("currentMachineType", {}).get("name", ""),
"recommended": overview.get("recommendedMachineType", {}).get("name", ""),
"monthly_savings": overview.get("costProjection", {}).get("cost", {}).get("units", "0")
})
return savings
This gives your team a weekly Slack digest of which VMs should be resized, with dollar amounts. No more guessing, no more forgotten over-provisioned VMs. ✅
📊 Quick Reference: Custom Machine Type Cheat Sheet
| Series | Custom Format | Memory Range | Extended Memory | SUDs |
|---|---|---|---|---|
| E2 | e2-custom-{cpu}-{memMB} | 0.5-8 GB/vCPU | No | No (but lowest base price) |
| N2 | n2-custom-{cpu}-{memMB} | 0.5-8 GB/vCPU | Yes (-ext suffix) | 20% max |
| N2D | n2d-custom-{cpu}-{memMB} | 0.5-8 GB/vCPU | Yes (-ext suffix) | 20% max |
| N1 | custom-{cpu}-{memMB} | 0.9-6.5 GB/vCPU | Yes (-ext suffix) | 30% max |
| N4 | n4-custom-{cpu}-{memMB} | 0.5-8 GB/vCPU | Yes (-ext suffix) | No |
⚠️ 5% premium: Custom machine types cost 5% more per vCPU and per GB compared to predefined types. But this only matters if the predefined type exactly matches your needs. If you're cutting resources by even 15%, the custom type saves money despite the premium.
💡 Quick Reference: What to Do First
| Action | Effort | Savings |
|---|---|---|
Run gcloud recommender to find oversized VMs |
5 min | Identifies all opportunities |
| Right-size dev/staging VMs with custom types | 15 min | 30-60% per VM |
| Create environment-aware machine type locals | 10 min | Prevents future over-provisioning |
Add rightsized label to track changes |
2 min | Proves savings to finance |
| Set up weekly Slack recommendations digest | 30 min | Continuous optimization |
Start with the gcloud recommender command. Run it right now. I guarantee you'll find at least 3 VMs that can be downsized immediately. 🎯
📊 TL;DR
Standard machine types = fixed menu, you pay for the whole plate
Custom machine types = order exactly what you need
Format = e2-custom-{vCPUs}-{memoryMB}
5% premium = custom costs 5% more per unit, but total is less
Recommender API = GCP tells you which VMs are oversized (free, 8-day data)
Not available for = MIGs, GKE, Dataflow, Dataproc, App Engine
Memory rules = multiple of 256 MB, 0.5-8 GB per vCPU (E2)
Machine type change = requires VM stop/start (plan for it)
Right-size non-prod first = biggest wins, lowest risk
Extended memory = N2/N1 only, use -ext suffix for high-RAM workloads
Bottom line: If your VMs are running predefined "standard" types from 6 months ago, they're almost certainly oversized. GCP's Recommender API tells you exactly what to change, and custom machine types let you pay for exactly what you need. That combination typically saves 30-60% per VM. 💰
Run gcloud recommender recommendations list on your staging project right now. Pick the VM with the biggest savings recommendation and change one line of Terraform. That's your first win. I'll wait. 😀
Found this helpful? Follow for more GCP cost optimization with Terraform! 💬
Top comments (0)