# Cloud Run vs GKE: How I Actually Choose

Cloud Run and GKE both run containers on GCP. Beyond that, they have different operational models, different cost structures, and different ceilings. Choosing between them isn't about which is "better" — it's about which fits the characteristics of what you're building.

Here's the framework I use, then the detail behind each decision point.

---

## The Decision Framework

Start here. If you hit a "yes" at any point, that's your answer.

```plaintext
Does your workload need any of these?
  → Custom networking (service mesh, pod-to-pod communication)
  → Stateful workloads (databases, persistent volumes)
  → Long-running background processes (not request-driven)
  → GPU/TPU workloads
  → Fine-grained autoscaling on custom metrics (queue depth, not just RPS)
  → Multi-container pods (sidecars beyond what Cloud Run supports)

YES to any → GKE is likely the right choice

NO to all → Cloud Run. Start there.
```

The bias toward Cloud Run is intentional. Most backend services — APIs, event consumers, async workers — don't need any of those things. Cloud Run handles them with less operational overhead and lower cost. GKE is the right answer when you've genuinely outgrown what Cloud Run offers, not when you want more control for its own sake.

---

## Cloud Run: What It's Good At

**Request-driven workloads.** Cloud Run scales from zero to many instances based on incoming request volume (HTTP or Pub/Sub push). No requests, no cost. This model fits APIs, webhook handlers, Pub/Sub consumers, and scheduled jobs triggered by Cloud Scheduler.

**Managed operations.** No nodes to patch, no cluster to upgrade, no control plane to monitor. Google manages the infrastructure. Your operational surface is the container and the Cloud Run configuration.

**Per-request billing.** You pay for the CPU and memory used during request processing, not for idle time. A service handling 1,000 requests per day costs almost nothing. The same service on a GKE node running 24/7 costs the node regardless of utilisation.

**Fast iteration.** Deploy a new container image, Cloud Run handles rollout, rollback is a traffic split away. The deploy-to-serving time is typically under 60 seconds.

**Where it hits limits:**

- **Concurrency ceiling.** Maximum 1,000 instances per service (adjustable by quota request), 80 concurrent requests per instance by default. For most workloads, this is more than enough. For very high-throughput pipelines, it can become a constraint.
- **Request timeout.** Maximum 60 minutes per request. Background jobs that run longer than this need a different approach.
- **No persistent storage.** Cloud Run instances are stateless. Local disk is ephemeral and small. For anything that needs persistent storage, you're writing to Cloud SQL, GCS, or another managed service.
- **Limited sidecar support.** Cloud Run supports multiple containers per instance (for sidecars like the Cloud SQL Auth Proxy, PgBouncer), but not the full multi-container pod model Kubernetes offers.

---

## GKE: What It's Good At

**Stateful workloads.** GKE supports persistent volumes, StatefulSets, and the full range of Kubernetes storage primitives. Running a self-hosted Redis, a message broker, or any workload that needs durable local storage belongs on GKE.

**Long-running processes.** Background workers that run continuously — not in response to HTTP requests but processing from a queue at their own pace — fit naturally on GKE. Cloud Run can do this via Pub/Sub pull, but GKE gives more control over the processing loop.

**Custom autoscaling.** GKE with KEDA (Kubernetes Event-Driven Autoscaling) or custom HPA metrics can scale on Pub/Sub queue depth, Redis list length, or any custom metric. Cloud Run scales on request rate — not directly on queue depth. If your scaling signal isn't incoming requests, GKE gives more flexibility.

**GPU/TPU workloads.** ML training, inference at scale, or any compute-intensive workload that needs accelerators runs on GKE. Cloud Run doesn't support GPUs.

**Complex networking.** Service mesh (Istio, Linkerd), pod-to-pod direct communication, custom network policies, multi-cluster networking — these are GKE's domain.

**Where it adds cost:**

- **Node cost is always on.** Even with Autopilot (GKE's managed node mode), you're paying for node capacity whether it's used or not. A GKE cluster with a 3-node minimum costs money continuously. Cloud Run with zero traffic costs nothing.
- **Operational overhead.** Cluster upgrades, node pool configuration, resource quota management, namespace structure — these are engineering time that Cloud Run doesn't require.
- **Slower time to first deploy.** A new GKE cluster takes 5–10 minutes to provision. Configuring workloads, services, ingress, and networking takes more time. Cloud Run is faster to get started.

---

## GKE Autopilot vs Standard

If you choose GKE, choose Autopilot unless you have a specific reason not to.

Standard GKE requires you to manage node pools — choose machine types, configure autoscaling, handle node upgrades. Autopilot manages nodes for you: you declare pods, GKE provisions the right nodes automatically.

The tradeoff: Autopilot has some restrictions (no privileged containers, limited node customisation) and costs more per pod than self-managed Standard nodes at high utilisation. For most teams that aren't running GKE at very high scale, Autopilot's operational simplicity is worth the premium.

---

## The Hybrid Pattern

Cloud Run and GKE aren't mutually exclusive. The most common pattern for teams that need both:

- **User-facing APIs and event consumers** → Cloud Run. Fast scaling, low cost, simple operations.
- **ML inference servers, long-running workers, or stateful services** → GKE. More control, persistent storage, custom scaling.

Both can exist in the same VPC, share the same Cloud SQL instance, and publish to the same Pub/Sub topics. The boundary is the workload characteristic, not the team or the product.

---

## The Signal That You've Outgrown Cloud Run

You're probably ready to move a specific workload to GKE when:

- You're hitting Cloud Run's request timeout limit on a meaningful fraction of requests
- You need scaling signals that aren't incoming request rate
- You're running workloads that need persistent local storage
- You need pod-level network policies or a service mesh
- Your Cloud Run costs are dominated by min_instances × idle time rather than actual request processing

None of these are common for API or event consumer workloads. They're common for ML pipelines, data processing workloads, and infrastructure services. Know your workload's characteristics before making the call.
