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.
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