Cloud Run Functions and Events
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
This page covers the function-as-a-service side of Google Cloud: how a function is written, deployed, secured and
triggered by HTTP requests or by events. A function is a Cloud Run service with a thin programming model on top, so
the platform behavior (revisions, scaling, networking, billing modes) is on Cloud
Run and Cloud Run scaling, traffic and jobs; this page
builds on them and does not repeat them. Examples target the gcloud CLI (release 586.0.0 of 2026-09-22), the Java
runtime java25 (the newest long-term-support Java base image listed for Cloud Run functions), functions-framework-api
version 1.0.4 and the Eventarc Standard edition, all verified on 2026-09-30 against the official documentation. Re-run
gcloud version and check the function runtimes table
before relying on exact values. The running Bookshelf scenario is described in
the section introduction. Here it contributes two functions: notifier, triggered
by messages on the Pub/Sub topic orders, which sends order-confirmation emails through a third-party provider
(SendGrid, with its API key in Secret Manager), and a thumbnail function triggered by uploads to the
bookshelf-uploads-$PROJECT_ID bucket. The project my-project-id, project number <project-number> and region
us-central1 are placeholders.
Cloud Functions became Cloud Run functions
The product name and the architecture changed more than once, so older articles disagree with each other. The timeline that matters:
| Date | Change |
|---|---|
Before 2022 |
Cloud Functions 1st gen: a separate product on Google-internal infrastructure, one request per instance, a small set of direct event sources. |
2022 onwards |
Cloud Functions 2nd gen: functions built on Cloud Run and Eventarc, with longer timeouts, larger instances, concurrency and traffic splitting. |
2024-08-22 |
Cloud Functions 2nd gen was renamed Cloud Run functions. Functions are now part of Cloud Run in the console, in billing and in documentation. |
2025-02-19 |
Functions can be deployed directly as Cloud Run services with |
2026-08-10 |
The |
Three things follow from the table. First, "Cloud Run functions" covers both functions created with the Cloud Functions
v2 API (gcloud functions deploy) and functions deployed through the Cloud Run API (gcloud run deploy --function);
both end up as a Cloud Run service. Second, 1st gen is still supported and can be created with
gcloud functions deploy --no-gen2, but no new design should start there. Third, older guides still show the
gcloud functions deploy flow with runtimes such as nodejs12, which has long been unsupported; the current
approach is the runtime list in the official documentation.
1st gen compared with Cloud Run functions
| Aspect | Cloud Run functions | 1st gen |
|---|---|---|
Infrastructure |
Cloud Run |
Google-internal infrastructure |
HTTP request timeout |
Up to 60 minutes |
Up to 9 minutes |
Event-driven timeout |
Up to 9 minutes with the Cloud Functions v2 API (Pub/Sub-backed delivery is bounded by the 600-second acknowledgement deadline) |
Up to 9 minutes |
Concurrency |
Up to 1,000 concurrent requests per instance |
One request per instance |
Instance size |
Up to 16 GiB of memory with 4 vCPU |
Up to 8 GB of memory with 2 vCPU |
Event sources |
90+ sources through Eventarc, including Cloud Audit Logs |
Direct support for 7 sources |
Event format |
CloudEvents |
Legacy background-function events, CloudEvents only in some languages |
Traffic splitting and revisions |
Supported |
Not supported |
Management API |
Cloud Run Admin API (functions deployed with |
Cloud Functions v1 API |
The comparison page is the authority for the full table, and the numbers there change as Cloud Run raises limits, so re-check it before relying on a limit. See Compare Cloud Run functions.
Function types and the Functions Framework
A Cloud Run function is source code plus an entry point: the name of a function (or the fully qualified class name in Java). At build time Google Cloud buildpacks combine your source with the Functions Framework, an open source library for each language that starts an HTTP server, translates incoming requests into calls of your function and takes care of logging and error handling. The same library runs on your laptop, so what you test locally is what runs in the cloud. The framework exists for Java, Node.js, Python, Go, .NET, Ruby and PHP.
There are two function signatures:
-
An HTTP function receives an HTTP request and writes an HTTP response. Use it for webhooks, small APIs and anything that needs a URL.
-
A CloudEvent function receives one event in the CloudEvents format. Use it when something happens in your project (a message is published, an object is uploaded) and you want to react.
In Java the two signatures are interfaces from the functions-framework-api artifact:
| Interface | Method |
|---|---|
|
|
|
|
BackgroundFunction (a legacy, non-CloudEvents signature that predates the CloudEvents interface) still compiles, but
new code should use CloudEventsFunction. The API artifact is provided-scope: the platform supplies the framework at
run time, so it is not packaged in your application.
<dependencies>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.0.4</version>
<scope>provided</scope>
</dependency>
<!-- CloudEvents API: the CloudEvent type received by CloudEventsFunction. -->
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-api</artifactId>
<version>${cloudevents.version}</version>
<scope>provided</scope>
</dependency>
<!-- Gson: parsing the event payload. -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
</dependencies>
Reference: Write Cloud Run functions. Take the
cloudevents.version and gson.version properties from the Maven Central pages of those artifacts, or from the
official function samples, rather than copying values from an older guide.
The shape of a CloudEvent
Every event arrives as an HTTP POST in the CloudEvents format. Eventarc uses the binary content mode: the event
attributes are HTTP headers and the body is only the event data. The Functions Framework reassembles the attributes and
the data into the CloudEvent object you receive.
| Attribute | Meaning |
|---|---|
|
Unique identifier of this event; repeated deliveries of the same event carry the same |
|
Where the event came from, for example the resource name of the topic or bucket. |
|
What happened, for example |
|
When the event occurred. |
|
Optional, narrows the event to a resource inside the source, such as an object name. |
|
Normally |
For a Pub/Sub event the data is the Pub/Sub message wrapped in a message object whose data field is Base64 encoded,
whose attributes map carries the message attributes, and whose messageId and publishTime identify the message.
Triggers
A function (or any Cloud Run service) can be started in four ways. The choice is the main design decision for an event-driven component, so it deserves a table.
| Trigger | How it works | Typical use |
|---|---|---|
HTTP |
A request to the function URL, from clients, other services, Cloud Scheduler or Workflows. Authorization is the Cloud Run IAM invoker check (see Authenticated invocation). |
Webhooks, small APIs, scheduled endpoints. |
Eventarc trigger |
Eventarc routes a CloudEvent to the service. Sources are direct events from Google Cloud services (Pub/Sub, Cloud Storage, Firestore and others) and Cloud Audit Logs entries for the 90+ services that write them. |
Reacting to changes in Google Cloud resources, with a uniform CloudEvents contract. |
Pub/Sub push subscription |
A subscription you own pushes each message as an HTTP request to the service, authenticating with an OIDC token of a service account. |
Fine control of the subscription (dead-letter topic, retry policy, ordering keys, message filters), or a Pub/Sub topic that also feeds other subscribers. |
Cloud Tasks, Cloud Scheduler or Workflows |
An explicit HTTP call. |
Rate-limited work queues, cron-like calls, orchestration (see Messaging and integration). |
Eventarc triggers
Eventarc is the managed event router. A trigger has three parts: a set of event filters (always a type, plus
attribute filters such as bucket for Cloud Storage), a destination (a Cloud Run service or function, a GKE service, a
Workflow or an HTTP endpoint) and a service account that Eventarc uses to deliver the event. For Cloud Run
destinations that service account needs roles/run.invoker on the target service. Rules that are good to know:
-
The trigger
--locationshould match the location of the resource being monitored (for Cloud Storage, the bucket’s region or the matching multi-region) and ideally the Cloud Run region. -
The event
typefilter cannot be changed after creation: delete and recreate the trigger. -
Events arrive as HTTP
POSTrequests; a non-success response (or a timeout) makes Eventarc retry with exponential backoff. Passing--max-retry-attempts=1at creation disables retries, which is rarely what you want. -
For Pub/Sub-backed delivery the acknowledgement deadline of the underlying subscription can be set up to 600 seconds, which bounds how long an event-driven function may run.
-
Use a user-managed service account for the trigger, not the default Compute Engine service account.
-
Eventarc Standard is the edition used here. Eventarc Advanced adds message buses, enrichment and pipelines; it is covered with the other integration products on Messaging and integration.
Reference: Create triggers with Eventarc.
Eventarc triggers or Pub/Sub push subscriptions
Both routes end in an authenticated HTTP POST, and Eventarc’s Pub/Sub trigger is itself implemented on top of a
Pub/Sub subscription. They differ in what you control and what you get for free.
| Aspect | Eventarc trigger | Pub/Sub push subscription |
|---|---|---|
Event sources |
Pub/Sub plus Cloud Storage, Firestore, Audit Logs and other Google sources, with one contract |
Pub/Sub topics only |
Payload |
CloudEvents (the Functions Framework hands you a |
Pub/Sub push envelope: a JSON body with a |
Subscription settings |
Managed for you; limited knobs (retry yes/no, a transport topic you may choose) |
Everything: dead-letter topic, retry policy, ordering keys, filters, ack deadline, expiration |
Function type |
CloudEvent function or any HTTP service |
Any HTTP service (an HTTP function parsing the envelope) |
Authentication |
Eventarc service account with |
Push authentication service account with |
Multiple destinations |
One trigger per destination |
Several subscriptions on one topic |
Rule of thumb: use an Eventarc trigger when the source is anything other than Pub/Sub or when you want the CloudEvents contract; use a push subscription when you need Pub/Sub features the trigger does not expose, for example a dead-letter topic for poison messages. Older guides show only the push subscription because Eventarc did not exist when they were written; it is still fully supported.
The Bookshelf notifier: Pub/Sub to a function to an email provider
orders-api publishes an order-created message to the topic orders. The notifier function receives it, builds a
confirmation email and sends it through SendGrid’s HTTPS API. The details of provider setup, domain authentication
(SPF, DKIM, DMARC) and deliverability belong to Email sending; this section
shows how the function is wired.
Design points that carry through the sections below:
-
The function is not publicly invocable. Only the trigger’s service account holds
roles/run.invoker. Older guides show an email function deployed with--allow-unauthenticated, which turns it into an open email relay that anyone on the internet can use. -
The SendGrid API key lives in Secret Manager and is exposed to the function through
--set-secrets, not through a plain--set-env-varsvalue that would be readable in the console and in revision metadata. -
The function runs as its own user-managed service account
notifier-sa, which holds onlyroles/secretmanager.secretAccessoron that one secret. The trigger uses a separate service accountnotifier-trigger-sa. -
Delivery is at least once, so the function must tolerate duplicates (see Idempotency).
IAM
| Principal | Role | Why |
|---|---|---|
|
|
Read the provider key at instance start. |
|
|
Call the function. |
|
|
Receive events from the Eventarc channel. |
|
|
Publish order messages. |
The person or pipeline deploying |
|
Deploy from source and attach the runtime service account. |
Cloud Build’s service account used for source deploys |
|
Build the image for a source deploy. |
Reference: Deploy Cloud Run functions. The full list of predefined roles is in Identity and access.
In the Google Cloud console
-
Select the project in the project picker at the top left, open the navigation menu and choose IAM & Admin, then Service Accounts. Create
notifier-saandnotifier-trigger-sa(name, ID, Create and continue, no project-level roles yet). Cloud Shell, opened from the top-right toolbar, is a quick way to run the gcloud steps below instead. -
Open Security, then Secret Manager, and create a secret named
sendgrid-api-key(Create secret, paste the value from your provider, Create secret). Open the secret, choose Permissions, then Grant access and givenotifier-sathe Secret Manager Secret Accessor role. -
Open Pub/Sub, then Topics, and create the topic
orderswith the default subscription cleared (Eventarc creates its own transport subscription). -
Open Cloud Run and choose Write a function. Enter the service name
notifier, the regionus-central1, the runtime Java 25, and for authentication leave Require authentication selected. Under Trigger, choose Add trigger, then Pub/Sub trigger, and set the event typegoogle.cloud.pubsub.topic.v1.messagePublished, the topicorders, the regionus-central1and the service accountnotifier-trigger-sa. Keep Retry on failure enabled. -
Under Containers, Volumes, Networking, Security choose the Security tab, set the Service account to
notifier-sa, and on the Variables & Secrets tab choose Reference a secret: nameSENDGRID_API_KEY, secretsendgrid-api-key, versionlatest, exposed as an environment variable. -
Replace the inline sample with your own source (the inline editor is intended for small functions; larger ones are deployed from a directory or a CI pipeline) and choose Create.
-
After the deployment finishes, open the Triggers tab of the service to confirm the trigger, and the Logs tab to see invocations.
With the gcloud CLI
PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe "$PROJECT_ID" --format="value(projectNumber)")
REGION=us-central1
gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com \
eventarc.googleapis.com pubsub.googleapis.com secretmanager.googleapis.com
Reference: gcloud services enable.
Create the identities and the secret, and grant the least-privilege bindings:
gcloud iam service-accounts create notifier-sa --display-name="Bookshelf notifier runtime"
gcloud iam service-accounts create notifier-trigger-sa --display-name="Bookshelf notifier trigger"
# The provider key; the value is read from standard input so it never lands in shell history
# (paste it, then press Ctrl+D)
gcloud secrets create sendgrid-api-key --replication-policy=automatic --data-file=-
gcloud secrets add-iam-policy-binding sendgrid-api-key \
--member="serviceAccount:notifier-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role=roles/secretmanager.secretAccessor
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:notifier-trigger-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role=roles/eventarc.eventReceiver --condition=None
gcloud pubsub topics create orders
References: gcloud iam service-accounts create,
gcloud secrets create,
gcloud pubsub topics create and
Create triggers from Pub/Sub events.
Deploy the function from the directory that contains pom.xml. The entry point is the fully qualified class name, and
--base-image selects the runtime and its automatic base-image updates:
gcloud run deploy notifier \
--source . \
--function com.example.bookshelf.notifier.NotifierFunction \
--base-image java25 \
--region "$REGION" \
--no-allow-unauthenticated \
--service-account "notifier-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--set-secrets SENDGRID_API_KEY=sendgrid-api-key:latest \
--set-env-vars MAIL_FROM=orders@bookshelf.example.com \
--max-instances 10
Reference: Deploy Cloud Run functions.
--no-allow-unauthenticated is the default for new services, but spelling it out in scripts makes the intent
reviewable. The flag --max-instances protects the email provider and your bill from a burst of orders.
Grant the invoker role and create the trigger:
gcloud run services add-iam-policy-binding notifier --region "$REGION" \
--member="serviceAccount:notifier-trigger-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role=roles/run.invoker
gcloud eventarc triggers create notifier-orders \
--location "$REGION" \
--destination-run-service notifier \
--destination-run-region "$REGION" \
--event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
--transport-topic="projects/${PROJECT_ID}/topics/orders" \
--service-account="notifier-trigger-sa@${PROJECT_ID}.iam.gserviceaccount.com"
References: gcloud run services
add-iam-policy-binding and gcloud eventarc
triggers create. Without --transport-topic Eventarc creates a topic of its own and the trigger would not listen to
orders.
Publish a test message and read the logs:
gcloud pubsub topics publish orders \
--message='{"orderId":"o-1001","email":"reader@example.com","total":"23.50"}' \
--attribute=eventType=order.created
gcloud run services logs read notifier --region "$REGION" --limit 20
References: gcloud pubsub topics publish and
gcloud run services logs read.
An equivalent with gcloud functions deploy
The Cloud Functions v2 command still works and creates the Eventarc trigger in the same step. Prefer it only for functions you already manage that way:
gcloud functions deploy notifier \
--gen2 --region "$REGION" --runtime java25 \
--source . --entry-point com.example.bookshelf.notifier.NotifierFunction \
--trigger-topic orders \
--service-account "notifier-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--set-secrets SENDGRID_API_KEY=sendgrid-api-key:latest
Reference: gcloud functions deploy. A function
created this way is managed through the Cloud Functions API; one deployed with gcloud run deploy --function is managed
purely through the Cloud Run API. Pick one per function and stay with it.
Java code
A CloudEvent function that decodes the Pub/Sub message, sends the email through the provider’s HTTPS API and fails loudly (so that Eventarc retries) when the provider returns an error:
package com.example.bookshelf.notifier;
import com.google.cloud.functions.CloudEventsFunction;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.cloudevents.CloudEvent;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.Map;
import java.util.logging.Logger;
public class NotifierFunction implements CloudEventsFunction {
private static final Logger LOG = Logger.getLogger(NotifierFunction.class.getName());
private static final Gson GSON = new Gson();
// Created once per instance and reused across invocations (connection reuse).
private static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
// Injected from Secret Manager by --set-secrets; never logged.
private static final String API_KEY = System.getenv("SENDGRID_API_KEY");
private static final String FROM = System.getenv("MAIL_FROM");
@Override
public void accept(CloudEvent event) throws Exception {
if (event.getData() == null) {
LOG.warning("Event " + event.getId() + " has no data; acknowledging without action");
return;
}
JsonObject envelope = GSON.fromJson(
new String(event.getData().toBytes(), StandardCharsets.UTF_8), JsonObject.class);
String encoded = envelope.getAsJsonObject("message").get("data").getAsString();
JsonObject order = GSON.fromJson(
new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8), JsonObject.class);
String orderId = order.get("orderId").getAsString();
String to = order.get("email").getAsString();
// The CloudEvent id is stable across redeliveries: use it (or orderId) as the idempotency key.
LOG.info("Sending confirmation for order " + orderId + " (event " + event.getId() + ")");
String body = GSON.toJson(Map.of(
"personalizations", java.util.List.of(Map.of("to", java.util.List.of(Map.of("email", to)))),
"from", Map.of("email", FROM),
"subject", "Your Bookshelf order " + orderId,
"content", java.util.List.of(Map.of("type", "text/plain",
"value", "Thank you for your order " + orderId + "."))));
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.sendgrid.com/v3/mail/send"))
.timeout(Duration.ofSeconds(20))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() / 100 != 2) {
// An exception makes the Functions Framework return a 5xx, so Eventarc retries the event.
throw new IllegalStateException("Email provider returned HTTP " + response.statusCode());
}
}
}
Reference: Write Cloud Run functions and the provider’s own API documentation for the request body. Application Default Credentials are not needed here because the function calls a third party with an API key; for Google Cloud calls (Cloud Storage, Firestore) the client libraries pick up the attached service account automatically. The keys never touch disk and no downloaded service-account key is involved.
Spring and Quarkus applications can also run as functions: the Spring Cloud Function GCP adapter and the Quarkus Google Cloud Functions extensions wrap a bean or a method in the same Functions Framework interfaces. The Quarkus options, including the native-image trade-offs, are on Serverless and Cloud Functions; when the application is more than one function, deploying the whole Spring Boot service to Cloud Run as a container is usually simpler (see Cloud Run).
Idempotency
Eventarc and Pub/Sub deliver at least once: a retry after a timeout, a slow acknowledgement or an instance restart can
deliver the same event twice, and nothing guarantees ordering. A confirmation email sent twice is annoying; a charge
applied twice is a defect. Make the handler idempotent: record a key such as orderId (or the CloudEvent id) in a
store with a conditional write (a Firestore create fails if the document exists, a PostgreSQL unique constraint
rejects the second insert) and skip work already done. The general delivery-guarantee reasoning is in
Delivery guarantees.
The Bookshelf thumbnail function: Cloud Storage to a function
When a user uploads a cover image to bookshelf-uploads-$PROJECT_ID, a function creates a thumbnail. The Cloud Storage
event type is google.cloud.storage.object.v1.finalized, sent when an object has been created or overwritten. The data
is the object’s metadata (bucket, name, contentType, size, generation), not the content: the function downloads
the object with the Cloud Storage client library.
Prerequisites specific to Cloud Storage triggers: the Cloud Storage service agent
(service-<project-number>@gs-project-accounts.iam.gserviceaccount.com) needs roles/pubsub.publisher on the project so
that Cloud Storage can publish events; the trigger service account needs roles/eventarc.eventReceiver and
roles/run.invoker; and the trigger location must match the bucket location. The function’s runtime service account
needs roles/storage.objectUser on the bucket to read the original and write the thumbnail (or
roles/storage.objectViewer plus roles/storage.objectCreator if you prefer to split them).
To avoid an infinite loop, write thumbnails to a different prefix and filter on it in the function, or better to a second bucket, because an Eventarc filter on Cloud Storage events selects by bucket and not by name prefix.
In the Google Cloud console
-
Open Cloud Storage, then Buckets, and confirm the bucket exists in
us-central1. Open IAM & Admin, then IAM, tick Include Google-provided role grants and grant Pub/Sub Publisher to the Cloud Storage service agent if it does not hold the role yet. -
Open Cloud Run, choose Write a function, or open the existing service
thumbnailer, and under Triggers choose Add trigger, then Cloud Storage trigger. -
Choose the event type google.cloud.storage.object.v1.finalized, the bucket, the region and the service account
thumbnailer-trigger-sa, then Save trigger.
With the gcloud CLI
BUCKET="bookshelf-uploads-${PROJECT_ID}"
STORAGE_AGENT="service-${PROJECT_NUMBER}@gs-project-accounts.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:${STORAGE_AGENT}" --role=roles/pubsub.publisher --condition=None
gcloud eventarc triggers create thumbnailer-uploads \
--location "$REGION" \
--destination-run-service thumbnailer \
--destination-run-region "$REGION" \
--event-filters="type=google.cloud.storage.object.v1.finalized" \
--event-filters="bucket=${BUCKET}" \
--service-account="thumbnailer-trigger-sa@${PROJECT_ID}.iam.gserviceaccount.com"
Java code
package com.example.bookshelf.thumbnailer;
import com.google.cloud.functions.CloudEventsFunction;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.cloudevents.CloudEvent;
import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;
public class ThumbnailerFunction implements CloudEventsFunction {
private static final Logger LOG = Logger.getLogger(ThumbnailerFunction.class.getName());
private static final Gson GSON = new Gson();
// Application Default Credentials: the attached runtime service account, no key file.
private static final Storage STORAGE = StorageOptions.getDefaultInstance().getService();
private static final String TARGET_BUCKET = System.getenv("THUMBNAIL_BUCKET");
@Override
public void accept(CloudEvent event) {
JsonObject object = GSON.fromJson(
new String(event.getData().toBytes(), StandardCharsets.UTF_8), JsonObject.class);
String bucket = object.get("bucket").getAsString();
String name = object.get("name").getAsString();
String contentType = object.has("contentType") ? object.get("contentType").getAsString() : "";
if (!contentType.startsWith("image/")) {
LOG.info("Skipping " + name + " (" + contentType + ")");
return;
}
byte[] original = STORAGE.readAllBytes(BlobId.of(bucket, name));
byte[] thumbnail = resize(original, 200);
STORAGE.create(BlobInfo.newBuilder(BlobId.of(TARGET_BUCKET, name))
.setContentType(contentType).build(), thumbnail);
LOG.info("Wrote thumbnail for gs://" + bucket + "/" + name);
}
private static byte[] resize(byte[] image, int maxSide) {
// Image scaling with javax.imageio omitted for brevity.
return image;
}
}
Reference: Cloud Storage client libraries and
Cloud Storage object resource. Add the
google-cloud-storage dependency through the libraries-bom. Object change notifications were deprecated on
2026-01-30; Eventarc (or Pub/Sub notifications) is the supported way to react to bucket changes.
The same pattern fits any "react to a file" flow, for example a function that starts a Dataflow template when a file lands in a bucket; the function stays tiny and the heavy lifting runs in the data service.
Audit Log and Firestore triggers
Two more Eventarc sources are worth knowing. Cloud Audit Logs triggers fire when a service writes an audit log entry,
which covers the 90+ services that have no direct events: filter on type=google.cloud.audit.log.v1.written, the
serviceName and the methodName (for example storage.objects.delete). Data Access audit logs must be enabled for
the service, and delivery is a little slower than for direct events. Firestore triggers fire on document create, update,
delete and written events and filter on database and a document path pattern. The event type names and filters are
listed on the Eventarc event reference.
gcloud eventarc triggers create audit-bucket-deletes \
--location "$REGION" \
--destination-run-service audit-reporter --destination-run-region "$REGION" \
--event-filters="type=google.cloud.audit.log.v1.written" \
--event-filters="serviceName=storage.googleapis.com" \
--event-filters="methodName=storage.buckets.delete" \
--service-account="audit-trigger-sa@${PROJECT_ID}.iam.gserviceaccount.com"
Reference: Eventarc event providers and destinations.
Pub/Sub push subscriptions
When you want to own the subscription, point it at the Cloud Run service URL. The subscription authenticates with an
OIDC identity token minted for a service account; Cloud Run verifies the token and the roles/run.invoker binding before
your code runs, so the function still needs no authentication code.
In the commands below notifier-http is an HTTP variant of the notifier deployed the same way with an HttpFunction. Because the delivery is a plain HTTP request rather than a CloudEvent, write the function as an HttpFunction (or a
Spring @RestController) that parses the push envelope: {"message": {"data": "<base64>", "messageId": "…",
"attributes": {…}}, "subscription": "…"}. Respond with 204 (or any 2xx) to acknowledge; any other status, or a
timeout, makes Pub/Sub redeliver according to the subscription’s retry policy.
In the Google Cloud console
-
Open Pub/Sub, then Subscriptions, and choose Create subscription.
-
Enter the subscription ID
orders-notifier-push, select the topicorders, set Delivery type to Push, paste the Cloud Run service URL as the Endpoint URL, and tick Enable authentication. -
Select the service account
notifier-push-sa. Leave the audience empty to use the endpoint URL, and set the Acknowledgement deadline to the longest the function can run (up to 600 seconds). -
Under Dead lettering, enable a dead-letter topic and a Retry policy with exponential backoff, then choose Create.
With the gcloud CLI
gcloud iam service-accounts create notifier-push-sa --display-name="Pub/Sub push to notifier"
gcloud run services add-iam-policy-binding notifier-http --region "$REGION" \
--member="serviceAccount:notifier-push-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role=roles/run.invoker
SERVICE_URL=$(gcloud run services describe notifier-http --region "$REGION" --format="value(status.url)")
gcloud pubsub topics create orders-dead-letter
gcloud pubsub subscriptions create orders-notifier-push \
--topic orders \
--push-endpoint "${SERVICE_URL}/" \
--push-auth-service-account "notifier-push-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--ack-deadline 600 \
--min-retry-delay 10s --max-retry-delay 600s \
--dead-letter-topic orders-dead-letter --max-delivery-attempts 5
References: Use Pub/Sub push with Cloud Run and
gcloud pubsub subscriptions create.
Dead-letter forwarding also needs the Pub/Sub service agent to hold roles/pubsub.publisher on the dead-letter topic and
roles/pubsub.subscriber on the subscription; the Pub/Sub page of this section lists the exact bindings. Projects created
after 2021-04-08 do not need the explicit roles/iam.serviceAccountTokenCreator grant to the Pub/Sub service agent that
older tutorials still show.
Authenticated invocation
HTTP functions are private by default: a request needs an identity token whose principal holds roles/run.invoker on the
service. Making a function public means granting that role to the special principal allUsers (what
--allow-unauthenticated does) and should be reserved for endpoints that are meant for the internet, ideally behind a
load balancer with Cloud Armor (see Load balancing and Cloud
Armor).
# Call a private function as yourself (your account needs roles/run.invoker, directly or by group)
URL=$(gcloud run services describe notifier-http --region "$REGION" --format="value(status.url)")
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" "$URL"
# Call it as a service account you may impersonate, the safer pattern for scripts
curl -H "Authorization: Bearer $(gcloud auth print-identity-token \
--impersonate-service-account=caller-sa@${PROJECT_ID}.iam.gserviceaccount.com \
--audiences="$URL")" "$URL"
Reference: Authenticate invocations to Cloud Run and
gcloud auth print-identity-token. The
second form needs roles/iam.serviceAccountTokenCreator on the impersonated account.
Other callers: another Cloud Run service uses the metadata server to fetch an identity token with the target URL as
audience (the client libraries do this); Cloud Scheduler and Workflows are configured with a service account and attach the
token themselves; browsers and mobile apps authenticate users through Identity-Aware Proxy, Identity Platform or your own
logic (see Identity and access). Functions created through the Cloud
Functions API have also used the role roles/cloudfunctions.invoker; on a function deployed to Cloud Run the effective
check is the Cloud Run invoker role on the service.
The inbound network can be restricted separately with ingress settings (--ingress internal, or
internal-and-cloud-load-balancing), which complements IAM rather than replacing it.
Configuration, secrets and environment variables
| Need | Mechanism |
|---|---|
Plain settings (feature flags, sender address) |
|
Secrets (API keys, passwords) |
|
Settings that change often without redeploying |
Read from Parameter Manager or a database at run time, see Runtime configuration |
Identity |
|
Memory, CPU, concurrency |
|
Scaling bounds |
|
Timeout |
|
Environment variables are visible to anyone who can read the revision, which is why a provider key belongs in Secret
Manager. Secrets exposed as environment variables are resolved when an instance starts, so a new secret version is picked
up by new instances only; mounted files can be refreshed without a restart when you reference latest. The Secret
Manager roles, versions and rotation are on Secret Manager and
Cloud KMS.
Concurrency and cold starts
Unlike 1st gen, a function instance can serve many requests at once (default 80, up to 1,000 with --concurrency). Java
functions benefit from a few settings: keep the Functions Framework entry-point class light and create clients in
static fields (as in the samples above), enable startup CPU boost (--cpu-boost) to speed up JVM start, set
--min-instances 1 for latency-critical HTTP functions, and mind that shared mutable state must be thread-safe once
concurrency is above one. Event-driven functions whose work per event is heavy or not thread-safe can be deployed with
--concurrency 1.
Runtimes and base-image updates
Cloud Run functions support Node.js, Python, Go, Java, Ruby, PHP and .NET. At the verification date the Java base images
were java25, java21, java17 and java11, Node.js nodejs24 (with 22 and 20 still supported and 26 in preview),
Python python314 and Go go127. Each runtime follows the language’s own lifecycle: Google deprecates a version
around six months before it decommissions it, and a decommissioned runtime can no longer be deployed or, after the
grace period, run. Java LTS releases are supported for years (the table lists the exact dates), while a version that the
language community has retired is removed.
Two stacks provide the operating system for the base images, referred to as google-24 and google-22. A function can
reference the image by short name (java25), by stack-qualified alias (google-24/java25) or by the full Artifact
Registry path. By default a deployed function opts in to automatic base image updates: Google patches the operating
system and runtime layers without a rebuild or a new revision of your code, which is a security benefit and the reason
to avoid custom images unless you need them. Opting out is possible when you must control every change of the image; the linked page
lists the flag and its effects.
Reference: Cloud Run functions runtimes. Because the tables change several times a year, plan a recurring check of the runtime support schedule for each function, ideally in the same dashboard that tracks other dependencies.
Local development, testing and CI
The Functions Framework runs the same HTTP server on your machine. For Java, add the Functions Framework Maven plugin and run the function in place:
<plugin>
<groupId>com.google.cloud.functions</groupId>
<artifactId>function-maven-plugin</artifactId>
<version>${function.maven.plugin.version}</version>
<configuration>
<functionTarget>com.example.bookshelf.notifier.NotifierFunction</functionTarget>
</configuration>
</plugin>
mvn function:run
# In another terminal: send a CloudEvent in binary mode with the Pub/Sub payload (the data is Base64 of the order JSON)
curl -X POST localhost:8080 \
-H "ce-id: 123" -H "ce-specversion: 1.0" -H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
-H "ce-source: //pubsub.googleapis.com/projects/my-project-id/topics/orders" \
-H "Content-Type: application/json" \
-d '{"message":{"data":"eyJvcmRlcklkIjoibzEwMDEiLCJlbWFpbCI6InJlYWRlckBleGFtcGxlLmNvbSJ9"}}'
Reference: Functions Framework and
functions-framework-java. Use the Pub/Sub emulator
(gcloud beta emulators pubsub start) when you need a real topic locally, and a mock HTTP server in place of the
email provider.
Unit tests call the function class directly. For an HttpFunction, mock HttpRequest and HttpResponse and capture
the writer; for a CloudEventsFunction, build a CloudEvent with CloudEventBuilder.v1() from the CloudEvents SDK. Keep
the HTTP call behind a small interface so tests substitute it. This is the same approach as any Java unit testing;
see Unit and integration testing for tools.
CI and deployment from GitHub Actions or GitLab should not use a downloaded service-account key. Use Workload Identity Federation, which exchanges the pipeline’s OIDC token for short-lived credentials (the setup is on Identity and access). A minimal GitHub Actions job:
name: deploy-notifier
on:
push:
branches: [main]
permissions:
contents: read
id-token: write # lets the job request an OIDC token
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '25'
- run: mvn -B verify
- uses: google-github-actions/auth@v3
with:
workload_identity_provider: projects/<project-number>/locations/global/workloadIdentityPools/github/providers/github
service_account: deployer-sa@my-project-id.iam.gserviceaccount.com
- uses: google-github-actions/setup-gcloud@v3
- run: >
gcloud run deploy notifier --source . --function com.example.bookshelf.notifier.NotifierFunction
--base-image java25 --region us-central1
Reference: Continuous deployment to Cloud Run and the google-github-actions/auth documentation. The deployer service account needs the roles listed in the IAM table above. Cloud Build can run the same pipeline when you prefer to stay inside Google Cloud; see Developer tools and DevOps.
Infrastructure as code
The function and its trigger are two resources. In Terraform (provider hashicorp/google, version 8 at the verification
date) a function deployed from a container image is a google_cloud_run_v2_service, and an Eventarc trigger is
google_eventarc_trigger:
resource "google_eventarc_trigger" "notifier_orders" {
name = "notifier-orders"
location = var.region
matching_criteria {
attribute = "type"
value = "google.cloud.pubsub.topic.v1.messagePublished"
}
destination {
cloud_run_service {
service = google_cloud_run_v2_service.notifier.name
region = var.region
}
}
transport {
pubsub {
topic = google_pubsub_topic.orders.id
}
}
service_account = google_service_account.notifier_trigger.email
}
Reference: Eventarc triggers with Terraform and the provider documentation. Source-based deployment with buildpacks is a CLI feature; with Terraform you deploy an image built by a pipeline. The wider IaC picture is on Infrastructure as code.
Migrating 1st gen functions
The supported route is the gcloud functions upgrade command, generally available since 2026-08-10. It works in stages so
that you can roll back until you commit:
# 1. Create a Cloud Run function copy of the 1st gen function, with equivalent configuration
gcloud functions upgrade my-function --region "$REGION" --setup-config
# 2. Redirect production traffic to the new copy and watch it
gcloud functions upgrade my-function --region "$REGION" --redirect-traffic
# (--rollback-traffic sends traffic back to the 1st gen copy)
# 3. When satisfied, finish: this deletes the original 1st gen function permanently
gcloud functions upgrade my-function --region "$REGION" --commit
# (--abort undoes every step so far and deletes the new copy)
Reference: Upgrade a 1st gen Cloud Run
function to Cloud Run and gcloud functions
upgrade. Things to check during the redirect step: the function URL changes (the new one is a run.app URL), event
triggers become Eventarc triggers, Pub/Sub-triggered functions need idempotent handlers because both copies may
briefly see events, legacy background-function signatures should move to CloudEventsFunction, and the runtime must be
a supported version. 1st gen remains supported with no announced shutdown date, so the migration is a planned
improvement (concurrency, longer timeouts, traffic splitting, 1,000 concurrent requests) rather than an emergency.
Choosing between a function, a service and a job
| Situation | Choice |
|---|---|
One small piece of logic reacting to one event type or one webhook |
Cloud Run function |
A web application or API with many routes, a framework such as Spring Boot or Quarkus |
Cloud Run service from a container (see Cloud Run) |
Batch processing that runs to completion, possibly in parallel |
Cloud Run job (see Cloud Run scaling, traffic and jobs) |
A long-running pull consumer |
Cloud Run worker pool |
A multi-step process with waiting and branching |
Workflows calling functions |
The comparison with other compute options is on Choosing a compute service. For the AWS analogue of this page, see Lambda: event-driven and serverless apps.
Pricing model
Cloud Run functions are billed as Cloud Run: you pay for the CPU, memory and requests an instance uses. Two billing modes exist. With request-based billing (the default for functions) you pay only while requests are being processed, plus a small per-request fee, and idle instances are free apart from reserved minimum instances; with instance-based billing you pay for the full instance lifetime and get CPU outside requests. Free monthly allowances for requests and compute time exist and are listed on the pricing page, together with the discounts for committed use. 1st gen functions have their own price list, based on invocations, compute time and networking. Costs outside the function matter as much as the function itself:
-
Eventarc events may be billed depending on the edition and source, and the Pub/Sub transport topic and subscription are billed at Pub/Sub rates for the volume of data delivered.
-
Cloud Build and Artifact Registry are used by every source deploy: build minutes and stored image bytes appear on the bill.
-
Secret Manager charges per active secret version and per access operation.
-
Networking: egress to the internet, to other regions and through a VPC connector or Direct VPC egress is billed under the network pricing rules.
-
Logging: verbose logs of every event can cost more than the function; log what you need.
Cost levers: lower --max-instances to cap a burst, reduce memory and CPU to what the workload needs, avoid
--min-instances unless latency requires it, batch events when the source allows it, and use budgets with alerts. Always
read the current numbers on Cloud Run pricing and
Eventarc pricing; this page deliberately states none, because they change
and vary by region.
Quotas and limits
Cloud Run functions inherit the limits of Cloud Run. The ones that most often surface in event-driven designs:
| Limit | Value or note |
|---|---|
Request timeout |
Up to 60 minutes for HTTP functions; event-driven functions follow the Pub/Sub acknowledgement deadline of at most 600 seconds |
Instance size |
Up to 16 GiB of memory with 4 vCPU on functions (the Cloud Run maximum is higher for plain services) |
Concurrency |
Default 80, maximum 1,000 per instance |
Maximum instances |
Default 100 per service, adjustable with |
Request size |
Large HTTP/2 uploads are supported; HTTP/1 request bodies are limited, so use Cloud Storage signed URLs for big files |
Eventarc |
Quotas on triggers per location and per project, and on event size |
Pub/Sub message |
Up to 10 MB; Eventarc payloads follow the same bound |
Check the Cloud Run quotas and Eventarc quotas pages for the current values, and request increases from IAM & Admin, then Quotas & System Limits.
Troubleshooting
| Symptom | Check |
|---|---|
|
The deployer and Cloud Build service account roles ( |
HTTP 403 from the function |
The caller lacks |
The trigger never fires |
|
Events delivered but the function returns 401 or 403 |
The trigger service account is missing |
The same event is processed repeatedly |
The function returned a non-2xx status or exceeded the acknowledgement deadline; fix the error or add idempotency, and set a dead-letter topic on push subscriptions. |
Secrets missing at runtime |
The runtime service account lacks |
Cold starts too slow |
Enable startup CPU boost, reduce class loading in the static initializers, raise |
Cannot deploy an old runtime |
The runtime was decommissioned; migrate to a supported base image. |
Mismatched project |
|
Read logs with gcloud run services logs read or in Cloud Run, then the service, then Logs. The Eventarc
Triggers page shows the transport and delivery metrics, and Cloud Monitoring graphs request counts, latency and instance
counts (see Monitoring and observability).
Where older guides are outdated
-
Articles titled "Cloud Functions" describe two products. The 1st gen model (one request per instance, nine minute limit, legacy background events) is still supported; the Cloud Run functions model has replaced it for new work since the rename on 2024-08-22.
-
Guides that deploy with
gcloud functions deployonly miss the direct route,gcloud run deploy --function, available since 2025-02-19, which exposes every Cloud Run feature. -
Runtimes shown in old examples (for instance
nodejs12,python37orjava11) are decommissioned or near the end of support; use the current list. -
Samples that create the function with
--allow-unauthenticatedand put an API key in--set-env-varsare insecure: keep functions private and secrets in Secret Manager. -
Container Registry (
gcr.io) shut down on 2025-03-18; source deploys store images in Artifact Registry. -
Guides that authenticate CI with a downloaded service-account key predate Workload Identity Federation for GitHub Actions and GitLab; new organizations also block key creation by default.
-
"Cloud Run for Anthos" is now Knative serving on GKE and is not a destination for these functions.
Clean up
Delete in dependency order; the Eventarc trigger first, because it holds the transport subscription:
gcloud eventarc triggers delete notifier-orders --location "$REGION" --quiet
gcloud eventarc triggers delete thumbnailer-uploads --location "$REGION" --quiet
gcloud run services delete notifier --region "$REGION" --quiet
gcloud run services delete thumbnailer --region "$REGION" --quiet
gcloud pubsub subscriptions delete orders-notifier-push --quiet
gcloud pubsub topics delete orders orders-dead-letter --quiet
gcloud secrets delete sendgrid-api-key --quiet
gcloud iam service-accounts delete "notifier-sa@${PROJECT_ID}.iam.gserviceaccount.com" --quiet
gcloud iam service-accounts delete "notifier-trigger-sa@${PROJECT_ID}.iam.gserviceaccount.com" --quiet
gcloud iam service-accounts delete "notifier-push-sa@${PROJECT_ID}.iam.gserviceaccount.com" --quiet
Reference: gcloud eventarc triggers delete.
Deleting a secret removes all its versions at once and cannot be undone. Source deploys also leave images in the
Artifact Registry repository cloud-run-source-deploy and sources in a Cloud Storage bucket created by the build; delete
those, and any thumbnail objects in the uploads buckets (remember Cloud Storage soft delete can retain deleted objects
for its retention period). The simplest cleanup for a learning project is to delete the whole throwaway project with
gcloud projects delete "$PROJECT_ID", which schedules removal after a 30-day recovery window.