After Google's 2024 rebrand, both products share the same underlying infrastructure, but differ in their deployment model. Cloud Run deploys containerised services that can handle multiple routes and custom runtimes via a Dockerfile, while Cloud Run functions (formerly Cloud Functions 2nd gen) is designed for single-purpose, event-driven handlers that require no Dockerfile. The deciding factor is not complexity but trigger type.
Use Cloud Run functions for anything triggered by a Google Cloud event — Pub/Sub messages, Cloud Storage events, Firestore writes, or Cloud Scheduler. Use Cloud Run for workloads that receive arbitrary HTTP traffic, need a custom base image, or must handle multiple routes efficiently. A complex webhook processor is still a better fit for Cloud Run functions than a simple multi-route API on Cloud Run.
Cloud Run handles up to 80 concurrent requests per instance by default (configurable to 1000), so one warm instance absorbs traffic bursts without spawning new instances. Cloud Run functions default to one request per instance, meaning 100 simultaneous Pub/Sub messages spawn 100 separate instances. This difference has a large impact on both cold-start frequency and cost at high event volumes.
Keeping a Cloud Run service at minimum 1 instance to avoid cold starts can cost around $12/month for very low-traffic workloads like a webhook handler receiving ~500 requests per day. That same traffic would cost fractions of a cent on Cloud Run functions because the scale-to-zero model matches infrequent, bursty request patterns perfectly. Right-size to the traffic pattern: sustained high-frequency traffic suits Cloud Run; low-frequency bursty traffic suits Cloud Run functions.
Migrating from Cloud Run functions to Cloud Run is straightforward: wrap the function handler in an HTTP server (Express for Node.js, Flask for Python), containerise it, and redeploy. Going the other direction is harder because Cloud Run functions enforce a single-entry-point model, requiring a multi-route app to be decomposed into separate functions. It is best to plan your architecture before committing to either model, especially if the service is expected to grow.
In August 2024, Google folded Cloud Functions (2nd gen) into the Cloud Run umbrella and rebranded it as 'Cloud Run functions'. That naming change tells you something important: under the hood, they now share the same infrastructure. But the developer experience, pricing model, and right use cases still differ significantly. I've deployed both in production at Commsult Indonesia — Cloud Run for our REST APIs and background workers, Cloud Run functions for event-driven Pub/Sub processors and webhook handlers. Here's the honest breakdown based on what I've actually run.
| Dimension | Cloud Run | Cloud Run Functions |
|---|---|---|
| Deployment model | Any container, custom Dockerfile | Managed function handler, no Dockerfile |
| Best trigger type | Arbitrary HTTP, multi-route APIs | GCP events: Pub/Sub, GCS, Firestore, Scheduler |
| Concurrency per instance | Up to 80+, configurable to 1000 | 1 by default, configurable to 1000 for HTTP |
| Cold start | 1-3s for a lean container, 30s+ with heavy ML deps | 200-800ms for lightweight runtimes |
| Cost sweet spot | High-frequency, sustained traffic | Low-frequency, bursty traffic |
| Portability | Same container runs on GKE, DigitalOcean, or local Docker | GCP-managed runtime only |
Before the rebrand, Cloud Functions (2nd gen) already ran on Cloud Run internally. Google made this explicit by merging the two products under one brand. Cloud Run functions is now Google's FaaS offering — single-purpose HTTP or event-triggered functions with a managed execution environment. Cloud Run is the container-based platform for anything more complex: multi-route APIs, long-running jobs, WebSocket servers, or workloads that need custom base images. The key difference is no longer infrastructure — it's the deployment model and how you think about your code.
Cloud Run deploys any container that listens on a port. You bring your own Dockerfile, pick any language runtime, bundle dependencies, and define health check endpoints. One Cloud Run service can handle multiple routes — you can deploy an entire Express or FastAPI app as a single service. Cloud Run supports up to 80 concurrent requests per instance by default (configurable up to 1000), which dramatically reduces cold starts for bursty traffic compared to per-request function isolation. Startup time depends on your container size — a lean Node.js container starts in 1-3 seconds; a heavy Python container with ML deps might take 10-15 seconds.
Cloud Run functions (formerly Cloud Functions 2nd gen) is optimized for single-purpose, event-driven execution. You write a function handler in Node.js, Python, Go, Java, Ruby, or PHP — no Dockerfile required. Triggers include HTTP requests, Pub/Sub messages, Cloud Storage events, Firestore writes, and Cloud Scheduler. The platform manages the runtime, handles concurrency internally, and scales to zero when idle. For a Pub/Sub consumer processing 100 messages per day, Cloud Run functions is dramatically simpler than maintaining a Cloud Run service with a Pub/Sub push subscription.
From my experience: use Cloud Run functions for anything triggered by a Google Cloud event (Pub/Sub, GCS, Firestore, Scheduler) and Cloud Run for anything that receives arbitrary HTTP traffic. The deciding factor is not complexity — it's trigger type. A complex webhook processor is still a better fit for Cloud Run functions than a simple multi-route API on Cloud Run. The moment you need a custom base image or want to handle multiple routes efficiently, switch to Cloud Run.
Both platforms bill for CPU, memory, and requests processed. The free tier includes 180,000 vCPU-seconds, 360,000 GiB-seconds, and 2 million requests per month in us-central1. For low-traffic workloads, both platforms cost essentially nothing. The divergence appears at scale. Cloud Run's support for 80+ concurrent requests per instance means one instance handles a burst of traffic that would spawn 80 separate Cloud Run functions instances. If your function takes 200ms to execute and costs $0.000000231 per vCPU-second, 1 million requests per day costs roughly $5-10/month — often cheaper than maintaining a Cloud Run service at minimum scale.
Cold starts are the primary operational concern with any serverless platform. Cloud Run functions cold starts run 200-800ms for lightweight runtimes (Node.js, Python without heavy imports). Cloud Run cold starts depend entirely on your container — a minimal container adds 1-3 seconds; a container loading a TensorFlow model might take 30+ seconds. Cloud Run's concurrency model helps: once an instance is warm, it handles concurrent requests without spawning new instances. For traffic with predictable spikes, set Cloud Run's minimum instances to 1 to eliminate cold starts entirely (costs roughly $10-15/month for a minimal CPU allocation).
┌─────────────────────────────────────────────────────────┐
│ Google Cloud Serverless Platforms │
├─────────────────────────┬───────────────────────────────┤
│ Cloud Run │ Cloud Run Functions │
│ (container-based) │ (event-driven FaaS) │
├─────────────────────────┼───────────────────────────────┤
│ • Any language/runtime │ • Managed runtime (no Docker) │
│ • Multi-route HTTP │ • Single-purpose handler │
│ • 80+ concurrent req │ • Event triggers (Pub/Sub etc) │
│ • Custom base image │ • Scale to zero by default │
│ • Longer cold starts │ • 200-800ms cold starts │
└─────────────────────────┴───────────────────────────────┘Cloud Run instances handle multiple concurrent requests. Cloud Run functions, by default, handle one request per instance (though you can now configure concurrency up to 1000 for HTTP-triggered functions). For a function processing a 10-second Pub/Sub message, with default concurrency, 100 simultaneous messages spawn 100 instances. With Cloud Run and concurrency set to 100, one instance handles all of them. This matters enormously for cost and cold start frequency when processing high-volume event streams.
I made this mistake early on: I deployed a Cloud Run service for a webhook handler that received maybe 500 requests per day. The service sat at minimum 1 instance to avoid cold starts, costing me $12/month for essentially idle compute. A Cloud Run function would have handled those 500 requests for fractions of a cent with zero cold start cost because the infrequent traffic pattern perfectly matches the scale-to-zero model. Right-size to the traffic pattern: high-frequency sustained traffic favors Cloud Run with concurrency; low-frequency bursty traffic favors Cloud Run functions with scale-to-zero.
At Commsult Indonesia, I use Cloud Run for all our web APIs and scheduled background jobs, and Cloud Run functions for any GCP event integration (Pub/Sub, GCS triggers, Firestore onCreate hooks). If I were starting a new project today, I'd default to Cloud Run for APIs and reach for Cloud Run functions only for event-driven glue code. Cloud Run's container model is more portable — you can run the same container on DigitalOcean, GKE, or a local Docker setup, making local development and debugging significantly easier.
Moving from Cloud Run functions to Cloud Run is straightforward: wrap your function handler in a simple HTTP server (Express for Node.js, Flask for Python), containerize it, and deploy to Cloud Run. The Pub/Sub trigger moves from a native function trigger to a Cloud Run push subscription. Moving from Cloud Run to Cloud Run functions is harder because Cloud Run functions enforce a single-entry-point model — you'd need to decompose a multi-route app into separate functions. Plan your architecture before committing to one model, especially if you expect the service to grow.
Sources & Further Reading