Skip to content

Choosing a runtime

Google Cloud gives you several places to run code. Most of them are Cloud Run underneath: containers that Google starts, scales and stops for you. They differ in what starts them and how long they live. Every one runs the same kind of Alchemy program, takes the same bindings, and runs as a service account of its own.

FunctionGoogle builds
Serviceon requests
Jobto completion
Worker poolalways on
GKEAutopilot
your program
you
you
you
you
you
image
Google
you
you
you
you
scaling
Google
Google
you
you
you
orchestration
Google
Google
Google
Google
you
OS & patching
Google
Google
Google
Google
Google
hardware
Google
Google
Google
Google
Google
From left to right, Google manages less and you decide more. None of them leaves the operating system to you.

Whichever you pick, you declare it with an id, its configuration and an Effect. Construction binds what it uses. A runtime that answers HTTP returns fetch, and one that runs to completion returns run:

src/reindex.ts
export default class Reindex extends GCP.Run.Job<Reindex>()(
"Reindex",
{ main: import.meta.url },
Effect.gen(function* () {
const db = yield* GCP.Firestore.ReadDatabase(Links);
return {
run: Effect.gen(function* () {
const { documents } = yield* db.list("links");
yield* Effect.log(`reindexing ${documents.length} links`);
}).pipe(Effect.orDie),
};
}).pipe(Effect.provide(GCP.Firestore.ReadDatabaseHttp)),
) {}

So moving part of an app from one runtime to another changes its declaration, not its bindings. Choose by how the work behaves:

A Cloud Run service runs your fetch handler in containers that start as requests arrive, serve many requests each, and stop when traffic does. An idle service costs nothing. Pub/Sub, Cloud Storage, Cloud Scheduler and Eventarc can all push to it, so it handles events as well as requests.

It’s an easy and cheap place to start, and it fits APIs, websites and event handlers.

instance 1up to 80 at oncerequestrequestrequestidle: scaled to zero
An instance takes many requests at once, and Cloud Run stops every instance when traffic does.

A job starts, does its work and exits. Nothing runs, or is billed, between executions. Start one from another runtime with the RunJob binding, or on a schedule with Cloud Scheduler.

It fits batch work: reindexing, exports, migrations, nightly reports.

task 0runs, then exitsnext executiontask 1runs, then exitsRunJob03:00
An execution runs its tasks to completion and exits. Between executions nothing runs and nothing is billed.

A worker pool keeps containers running whether or not anyone calls them, and has no URL at all. You pay for them while they run.

It fits workers that pull from Pub/Sub around the clock, or hold a connection open.

messagesinstance 1running · pulling from Pub/Subinstance 2running
A fixed number of instances run whether or not messages arrive, so there's no cold start, and you pay for every hour they run.

A Cloud Function is a Cloud Run service whose image Google builds from your bundled code, instead of your local Docker. Deploys take a few minutes longer, and need no Docker on the machine running them.

It fits deploying from somewhere Docker isn’t available.

Cloud Runservicedocker buildpushdeployCloud FunctionuploadGoogle builds the imagedeploy
Both end as a Cloud Run service. A Function skips Docker on your machine, and its deploy waits on Google's build instead.

GKE runs a Kubernetes cluster, in Autopilot mode with Google managing the nodes. You deploy Deployments and Jobs from the same Stack, and bindings grant roles to each pod’s identity through Workload Identity.

A cluster takes about ten minutes to create and costs money while it exists. Choose it when you want Kubernetes itself: Helm charts, long-lived pods, or workloads you already run on it.

An app rarely needs just one. A common shape is a service for the API, a topic it publishes to, a worker pool that consumes it, and a job that runs nightly:

RunJobApiCloud Run serviceEventsPub/Sub topicWorkerworker poolReindexCloud Run job

Each runtime runs as its own service account, granted only the roles its own bindings ask for.