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 gcloud run deploy --source . --function ENTRYPOINT --base-image BASE_IMAGE. These functions are managed with the Cloud Run Admin API and every Cloud Run feature is available.

2026-08-10

The gcloud functions upgrade tool for moving 1st gen functions to Cloud Run reached general availability.

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 gcloud run deploy) or Cloud Functions v2 API

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

com.google.cloud.functions.HttpFunction

void service(HttpRequest request, HttpResponse response) throws Exception

com.google.cloud.functions.CloudEventsFunction

void accept(CloudEvent event) throws Exception

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

id

Unique identifier of this event; repeated deliveries of the same event carry the same id, which makes it the natural idempotency key.

source

Where the event came from, for example the resource name of the topic or bucket.

type

What happened, for example google.cloud.pubsub.topic.v1.messagePublished or google.cloud.storage.object.v1.finalized.

time

When the event occurred.

subject

Optional, narrows the event to a resource inside the source, such as an object name.

datacontenttype and data

Normally application/json, with a payload that depends on the event type.

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 --location should 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 type filter cannot be changed after creation: delete and recreate the trigger.

  • Events arrive as HTTP POST requests; a non-success response (or a timeout) makes Eventarc retry with exponential backoff. Passing --max-retry-attempts=1 at 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.

flowchart LR subgraph Sources["Event sources"] PS["Pub/Sub topic: orders"] GCS["Cloud Storage: bookshelf-uploads"] AL["Cloud Audit Logs"] FS["Firestore"] end PS --> T1["Eventarc trigger"] GCS --> T2["Eventarc trigger"] AL --> T3["Eventarc trigger"] FS --> T4["Eventarc trigger"] T1 -->|"CloudEvent over HTTP POST,\nrun.invoker identity token"| N["Cloud Run function: notifier"] T2 -->|"CloudEvent"| TH["Cloud Run function: thumbnailer"] T3 -->|"CloudEvent"| X["Cloud Run service or function"] T4 -->|"CloudEvent"| X N -->|"2xx = acknowledged\nnon-2xx or timeout = retry"| T1

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 CloudEvent)

Pub/Sub push envelope: a JSON body with a message object and a subscription field

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 roles/run.invoker

Push authentication service account with roles/run.invoker; the Pub/Sub service agent mints the OIDC token

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.

sequenceDiagram participant O as orders-api participant P as Pub/Sub topic orders participant E as Eventarc trigger participant N as notifier (Cloud Run function) participant S as Secret Manager participant M as Email provider O->>P: publish order-created message P->>E: deliver to transport subscription E->>N: CloudEvent over HTTPS (identity token of notifier-trigger-sa) N->>S: read sendgrid-api-key (injected at instance start) N->>M: POST /v3/mail/send M-->>N: 202 Accepted N-->>E: 2xx, event acknowledged

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-vars value 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 only roles/secretmanager.secretAccessor on that one secret. The trigger uses a separate service account notifier-trigger-sa.

  • Delivery is at least once, so the function must tolerate duplicates (see Idempotency).

IAM

Principal Role Why

notifier-sa (the function’s runtime identity)

roles/secretmanager.secretAccessor on the secret sendgrid-api-key only

Read the provider key at instance start.

notifier-trigger-sa (the Eventarc identity)

roles/run.invoker on the service notifier

Call the function.

notifier-trigger-sa

roles/eventarc.eventReceiver on the project

Receive events from the Eventarc channel.

orders-api runtime service account

roles/pubsub.publisher on the topic orders

Publish order messages.

The person or pipeline deploying

roles/run.sourceDeveloper, roles/serviceusage.serviceUsageConsumer and roles/iam.serviceAccountUser on the runtime service account

Deploy from source and attach the runtime service account.

Cloud Build’s service account used for source deploys

roles/run.builder

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

  1. Select the project in the project picker at the top left, open the navigation menu and choose IAM & Admin, then Service Accounts. Create notifier-sa and notifier-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.

  2. 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 give notifier-sa the Secret Manager Secret Accessor role.

  3. Open Pub/Sub, then Topics, and create the topic orders with the default subscription cleared (Eventarc creates its own transport subscription).

  4. Open Cloud Run and choose Write a function. Enter the service name notifier, the region us-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 type google.cloud.pubsub.topic.v1.messagePublished, the topic orders, the region us-central1 and the service account notifier-trigger-sa. Keep Retry on failure enabled.

  5. 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: name SENDGRID_API_KEY, secret sendgrid-api-key, version latest, exposed as an environment variable.

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

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

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

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

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

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

  2. Open Cloud Run, choose Write a function, or open the existing service thumbnailer, and under Triggers choose Add trigger, then Cloud Storage trigger.

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

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

  1. Open Pub/Sub, then Subscriptions, and choose Create subscription.

  2. Enter the subscription ID orders-notifier-push, select the topic orders, set Delivery type to Push, paste the Cloud Run service URL as the Endpoint URL, and tick Enable authentication.

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

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

--set-env-vars KEY=VALUE, --update-env-vars, --remove-env-vars; --env-vars-file for a YAML file

Secrets (API keys, passwords)

--set-secrets ENV_NAME=SECRET:VERSION for an environment variable, or --set-secrets /path/file=SECRET:VERSION to mount a file; always pin a version in production rather than latest if rotation must be controlled

Settings that change often without redeploying

Read from Parameter Manager or a database at run time, see Runtime configuration

Identity

--service-account, the runtime identity used for Application Default Credentials

Memory, CPU, concurrency

--memory, --cpu, --concurrency

Scaling bounds

--min-instances (avoids cold starts, billed while idle), --max-instances

Timeout

--timeout in seconds

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 --max-instances and a quota request

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

gcloud run deploy --function fails to build

The deployer and Cloud Build service account roles (roles/run.sourceDeveloper, roles/run.builder), the entry-point class name, and that functions-framework-api is on the classpath.

HTTP 403 from the function

The caller lacks roles/run.invoker, or the identity token audience is wrong. Fetch a token for the service URL and try gcloud run services get-iam-policy.

The trigger never fires

gcloud eventarc triggers describe for the state and filters; verify the trigger location matches the source location, the bucket or topic is right, and the Cloud Storage service agent holds roles/pubsub.publisher.

Events delivered but the function returns 401 or 403

The trigger service account is missing roles/run.invoker on that service.

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 roles/secretmanager.secretAccessor, or the secret has no enabled version; the revision fails to start with a clear message in the logs.

Cold starts too slow

Enable startup CPU boost, reduce class loading in the static initializers, raise --min-instances, or consider a native image through Quarkus.

Cannot deploy an old runtime

The runtime was decommissioned; migrate to a supported base image.

Mismatched project

gcloud config get-value project; functions and triggers live in the project of the active configuration.

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 deploy only 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, python37 or java11) are decommissioned or near the end of support; use the current list.

  • Samples that create the function with --allow-unauthenticated and put an API key in --set-env-vars are 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.