Deliver Pub/Sub messages to a Cloud Run service, with the failure paths closed
Six steps for a delivery path that survives contact with production: decide push or pull first, make the consumer idempotent because the default guarantee requires it, pick the Cloud Run resource type, configure the dead-letter topic before go-live, authorise the delivery, then verify by failing on purpose.
markdownguidev1QYGE-0A5Ystage: buildtrust: officialfamily: messagingservice: pubsubinterface: anypub/sub to cloud runpush subscriptionpull subscriptionidempotent consumerdead-letter topicworker poolat-least-onceroles/run.invokerordering key
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.