Email copiado — support@tuurt.com
Cargando experiencia
gcp · August 27, 2026 · 6 min

Cloud Run vs GKE Autopilot: How We Decide Where to Deploy a New Microservice

We compare Cloud Run and GKE Autopilot on what actually changes an architecture decision: cold starts, networking control, and the point where a cluster starts to earn its keep.

By Tuurt Team

Cloud Run vs GKE Autopilot: how we decide where to deploy a new microservice

Every time we spin up a new microservice on GCP, the first technical question we settle isn't language or framework, it's destination: Cloud Run or GKE Autopilot? Both platforms promise to take infrastructure management off the table -no nodes to patch, no cluster to size by hand- but they start from a different mental model, and mixing them up leads to decisions that come due months later, once the service already has real traffic and switching platforms stops being trivial.

This article walks through the criteria we actually use to decide, not an abstract feature comparison.

The mental model: stateless container vs a real Kubernetes cluster

Cloud Run runs individual containers without the team ever having to think about Kubernetes. You define a container, how much resource it needs, an instance range, and the platform handles the rest: routing, TLS, per-request scaling, deployment retries. There's no kubectl, no Deployment or Service manifests, no deciding a nodeSelector or a PodDisruptionBudget.

gcloud run deploy pedidos-api \
  --image gcr.io/my-project/pedidos-api:latest \
  --region us-central1 \
  --min-instances 0 \
  --max-instances 20 \
  --memory 512Mi \
  --cpu 1 \
  --allow-unauthenticated

GKE Autopilot is still real Kubernetes: the same YAML manifests, the same API, the same objects (Deployment, Service, HPA, Ingress) as a GKE Standard cluster, except Google manages the nodes and bills for the resources each pod requests instead of for the capacity of the whole node. If the team already knows Kubernetes, moving existing workloads to Autopilot is nearly mechanical. If it doesn't, Autopilot doesn't remove that learning curve, it only removes the part about operating nodes.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pedidos-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pedidos-api
  template:
    metadata:
      labels:
        app: pedidos-api
    spec:
      containers:
        - name: pedidos-api
          image: gcr.io/my-project/pedidos-api:latest
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"

This difference in mental model is what weighs most at the start: Cloud Run is built for a small team to ship fast without adopting Kubernetes; Autopilot is built for a team that already operates on Kubernetes to stop operating nodes.

Cold starts and the min/max instance range

On Cloud Run, once a revision scales to zero, the next request pays the cost of starting a new container: unpacking the image, initializing the runtime, running whatever startup code the application has. That cost varies by language and image size -a compiled Go binary starts up very differently from a JVM application with a large classpath- and it's the reason setting --min-instances above zero is the first lever we reach for once a service has steady traffic and first-request latency matters. With min-instances set to 1 or more, Cloud Run keeps that many instances running permanently and scale-to-zero no longer applies below that floor.

On GKE Autopilot, the equivalent of a "cold start" is different: it isn't starting a container from scratch on every request, it's how long the scheduler takes to place a new pod once the Horizontal Pod Autoscaler decides to scale up, and that time depends on whether Autopilot already has compute capacity ready or needs to provision it. For a Deployment with a fixed minimum replica count, that cost doesn't sit on the path of every request the way it does on Cloud Run: the pods are already running, and the HPA only comes into play once traffic climbs past what the current replicas can absorb.

Put differently: on Cloud Run, scaling to zero is the default and you have to explicitly opt out of it; on Autopilot, keeping minimum replicas running is the default, and scaling to zero requires extra setup (KEDA, for instance) that we rarely reach for in practice because most of the workloads we run on Kubernetes already carry steady traffic.

Control over networking and sidecar containers

Cloud Run supports multi-container deployments -a main container plus one or more sidecars sharing the same lifecycle and network namespace- which covers cases like an authentication proxy or a metrics agent running alongside the service. But network control is still that of a managed platform: talking to resources inside a VPC (a Cloud SQL instance over a private IP, an internal service) requires a VPC Access connector, and there's no access to low-level networking primitives like NetworkPolicies or fine-grained control over the CNI.

GKE Autopilot gives access to native Kubernetes networking objects -Service, Ingress, NetworkPolicy, Gateway API- and to a real namespace where you can run a service mesh like Istio or Cloud Service Mesh if the project needs one. The tradeoff is that Autopilot imposes deliberate restrictions on what a pod can do: it doesn't allow privileged containers, it restricts hostNetwork and hostPort usage, and it doesn't support general-purpose DaemonSets the way a Standard cluster does, precisely because Google has to maintain isolation guarantees between the workloads of different customers sharing the same managed node pool. A team that needs a real DaemonSet -say, for a logging agent running on every node with direct access to the host filesystem- hits that restriction quickly, and that's where GKE Standard comes back into the conversation.

The point where a cluster starts to earn its keep

For a new, stateless microservice with HTTP traffic and no exotic networking dependencies, we almost always start on Cloud Run: time to first deploy is shorter, there's no YAML to maintain, and the cost under low or intermittent traffic is hard to beat because you pay for actual usage, not reserved capacity.

The conversation changes when some of these conditions show up -usually not just one, they tend to show up together: the number of related services grows large enough that sharing a network namespace and a service mesh across them is worth more than deploying them in isolation; the team needs Kubernetes primitives that Cloud Run doesn't expose, like Jobs with step dependencies, StatefulSets for stateful workloads, or CRDs from a specific operator; or the team already has experience running Kubernetes on other projects and standardizing cuts more cognitive load than it costs to operate the extra cluster. None of those conditions shows up on day one of a new microservice, which is why we rarely start there directly: it's cheaper to migrate from Cloud Run to Autopilot once the project actually calls for it -the container images don't change, only the deployment manifest does- than to operate a cluster from day one for a service that doesn't yet know how much traffic it's going to see.

The rule we apply

The question we ask before choosing isn't "which platform is better?" but "what does this service need that the other platform can't give it natively?" If the answer is "nothing yet," Cloud Run. If the answer involves networking shared across several services, specific Kubernetes primitives, or a team that already operates clusters and wants one operating model for everything, Autopilot. Choosing Autopilot upfront for a service that doesn't need it yet is the most common way a team ends up maintaining Kubernetes YAML for a problem a single gcloud run deploy command would have solved just as well.

gcp kubernetes cloud-run devops
← Back to blog