The design is docs/architecture/23-ops.md section 6. Workers run a reduced
web tier. Feature parity on Workers is out of scope (PRD section 12): what
this target cannot do is refused when it starts, by name.
Status in this release
The configuration is in place (apps/web/wrangler.jsonc, the
cloudflare-module Nitro preset, the Hyperdrive bridge and the start-up
checks). A Worker needs S3-compatible storage for R2
(QUIRE_STORAGE_DRIVER=s3), a realtime driver across requests
(QUIRE_REALTIME_DRIVER=durable_objects or centrifugo), a shared cache
(QUIRE_CACHE_DRIVER=postgres or valkey) and an HTTP email provider.
Without them the Worker refuses to start and its log names each setting.
The Durable Objects driver is a client of the realtime Worker in
apps/realtime-worker (one Durable Object per channel for fan-out, presence
and history, one per person for disconnects); deploy it alongside, as below,
or use Centrifugo.
The pieces
Piece
On Cloudflare
web
A Worker with nodejs_compat
Postgres
External, reached through Hyperdrive: HYPERDRIVE for the application role, REPORT_HYPERDRIVE for the report role on that same physical database. The web tier copies each connection string into DATABASE_URL and QUIRE_REPORT_DATABASE_URL when it starts
Files
R2, through its S3 API (S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com); the FILES binding attaches the bucket
Background jobs
pg-boss over Hyperdrive when the job must be enqueued with a write. With QUIRE_QUEUE_DRIVER=cloudflare on the companion worker, the light jobs (unordered notification and webhook deliveries) go through Cloudflare Queues instead, so Postgres is not polled for them. The companion worker runs both
Realtime
The realtime Worker, apps/realtime-worker, with Durable Objects
A companion container host. A Worker cannot run them
Tracing
Workers observability, enabled in wrangler.jsonc
REPORT_HYPERDRIVE supplies the report role for the physical database named
by HYPERDRIVE. This target does not serve tenants pinned to additional
physical databases, as described below.
What this target cannot do
Refused at start, with every problem listed at once:
No SMTP. Use an HTTP provider in QUIRE_EMAIL_PROVIDER_CONFIG.
No local disk.QUIRE_STORAGE_DRIVER must name object storage.
No in-process realtime or cache. Workers share no memory between
requests, so QUIRE_REALTIME_DRIVER=inprocess and
QUIRE_CACHE_DRIVER=memory are refused.
No ClamAV, Gotenberg or ffmpeg in the Worker.CLAMAV_URL,
GOTENBERG_URL and FFMPEG_PATH set on the Worker are refused; set them on
the companion worker instead.
No dedicated-database organisations. A Worker’s Hyperdrive bindings are
fixed at deploy time, so an organisation with its own database gets a clear
“unavailable here” page. Serve it from Compose or Vercel.
And one thing that is not refused but must be known: prerendering and
incremental static regeneration do not work on Workers, whatever the
framework documentation says. Every route renders per request.
On this target keep transactions short and never hold one across a network
call: Hyperdrive resets session state when a connection returns to the pool,
so the tenant context is set per transaction.
Deploying
Create the resources:
bun run --bun wrangler hyperdrive create quire-app --connection-string="postgres://quire_app:...@db.example.com:5432/quire"bun run --bun wrangler hyperdrive create quire-report --connection-string="postgres://quire_report:...@db.example.com:5432/quire"bun run --bun wrangler r2 bucket create quire-filesbun run --bun wrangler queues create quire-jobs
Put the two Hyperdrive ids into apps/web/wrangler.jsonc.
Set the secrets, one at a time with bun run --bun wrangler secret put <NAME> from
apps/web: QUIRE_SECRET_KEY, QUIRE_MASTER_KEY,
QUIRE_EMAIL_PROVIDER_CONFIG, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY,
QUIRE_COLLAB_SIGNING_KEY, QUIRE_REALTIME_WORKER_SECRET. Plain settings
(QUIRE_APP_ORIGIN, QUIRE_CONTENT_ORIGIN, QUIRE_PLATFORM_DOMAINS,
QUIRE_DATABASE_ID, S3_ENDPOINT, S3_BUCKET, QUIRE_COLLAB_URL) go in
vars.
Build and deploy from apps/web:
NITRO_PRESET=cloudflare-module bun run buildbun run --bun wrangler deploy
Deploy the realtime Worker, with the web tier’s QUIRE_REALTIME_WORKER_SECRET
and its token secret as QUIRE_REALTIME_TOKEN_SECRET (the web tier signs
realtime tokens with its own QUIRE_REALTIME_TOKEN_SECRET, or with
QUIRE_SECRET_KEY when that is unset, so use whichever it uses), and set
QUIRE_REALTIME_WORKER_URL on the web tier to its address:
cd apps/realtime-workerbun run --bun wrangler secret put QUIRE_REALTIME_WORKER_SECRETbun run --bun wrangler secret put QUIRE_REALTIME_TOKEN_SECRETbun run --bun wrangler deploy
For light jobs on Cloudflare Queues, create one queue per light queue and
set these on the companion worker (an API token with Queues read and
write):
bun run --bun wrangler queues create quire-events-notificationsbun run --bun wrangler queues create quire-events-notifications-deadbun run --bun wrangler queues create quire-events-webhooksbun run --bun wrangler queues create quire-events-webhooks-dead
QUIRE_QUEUE_DRIVER=cloudflare, CLOUDFLARE_ACCOUNT_ID,
CLOUDFLARE_QUEUES_TOKEN, and QUIRE_QUEUE_PREFIX if not quire-.
Run the companion host as in the Vercel guide, step 3.
Migrations run there, before each Worker deployment.
A refused configuration shows in bun run --bun wrangler tail as “The web tier did
not start on cloudflare”, followed by each setting to change.