---
cuppy_hub: gcp
cuppy_path: guides/messaging/deliver-pubsub-to-cloud-run.md
cuppy_section: docs
cuppy_doc: QYGE-0A5Y
cuppy_doc_uuid: ebe79f9e-48c4-496a-b24f-5cde23dcd7d0
cuppy_permalink: https://cuppy.io/gcp/d/QYGE-0A5Y
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/gcp/docs/guides/messaging/deliver-pubsub-to-cloud-run
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 9f0522ebd3157972f4a4921b2eb05f2d8314ff46c5dbda14a7c870671d485cf5
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Pub/Sub to Cloud Run

## Step 1 — push or pull, decided deliberately

| | Push | Pull |
|---|---|---|
| who sets the pace | **Pub/Sub** | **the consumer** |
| what you run | a Cloud Run **service** with an endpoint | a consumer that fetches |
| failure mode | your service's 429/503 becomes a redelivery | your consumer's backlog |

**Push when the work is small and bursty and you want nothing to run idle. Pull when the downstream is fragile
or rate-limited and the consumer must control the flow.** Changing your mind later is a subscription change,
which is cheap — but it changes which Cloud Run resource type you need, which is not.

## Step 2 — pick the Cloud Run resource type to match

- **push** → a Cloud Run **service**: it is request-driven and scales on requests, which is exactly what a
  push subscription produces;
- **pull** → a **worker pool**: no public endpoint, and it does **not** scale on requests, which is correct
  because there are none.

A pull consumer deployed as a service will not scale, because no requests arrive
(`reference/compute/cloud-run.md` §1).

## Step 3 — make the consumer idempotent. This is not optional

Standard subscriptions are **at-least-once**. The consumer **will** see a message twice. Give every message a
natural key and make processing safe to repeat.

Only after that is true, consider enabling **exactly-once delivery** — as a reduction in noise, never as the
thing that makes correctness possible.

## Step 4 — configure the dead-letter topic now, not after the first incident

A subscription without one retries a permanently-failing message until retention expires, and then the message
is **gone with no record**. Create the dead-letter topic, point the subscription at it, and give it a
subscription of its own so somebody sees what lands there.

## Step 5 — authorise the delivery

For a **push** subscription the Pub/Sub push identity must be allowed to invoke the service —
`roles/run.invoker` on it. Keep the service otherwise **private**: a push endpoint open to the internet is an
unauthenticated write path into your processing.

For a **pull** consumer, the worker pool's own service account needs subscriber permission on the subscription.

## Step 6 — verify by failing on purpose

| Test | Expected |
|---|---|
| publish one message | processed once |
| publish the **same** message twice | **one** net effect. If there are two, step 3 is not done |
| make the consumer fail permanently for one message | the message lands in the **dead-letter topic** |
| load it past the instance ceiling (push) | **429** — capacity. Raise maximum instances, or move to pull |
| break the secret the consumer reads | **503** — configuration, not capacity |
| if ordering is on, publish many messages with **one** ordering key | throughput serialises. That is the trade; use a high-cardinality key |

The second and third rows are the ones people skip, and they are the two that decide whether this path is
production-grade.

## For a migration

Before building any of this, check whether you need a consumer at all: a **BigQuery**, **Bigtable** or **Cloud
Storage** subscription writes messages straight into those services with no code
(`reference/messaging/pubsub.md` §2). A migrated architecture frequently carries a consumer whose entire job
Pub/Sub will now do. See `guides/parity/messaging.md`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 91 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/gcp/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
