Skip to content

The web tier on Vercel

Run the Quire web tier on Vercel with a companion worker.

The design is docs/architecture/23-ops.md section 5. Vercel runs the web tier only. Everything else runs on a companion host you operate, and it is not optional.

Status in this release

The configuration is in place (apps/web/vercel.json, the vercel Nitro preset and the start-up checks). Vercel needs shared file storage (QUIRE_STORAGE_DRIVER=s3 or azure), a realtime driver that works across function instances (QUIRE_REALTIME_DRIVER=sse or centrifugo) and an HTTP email provider. Without them the web tier refuses to start and its log names each missing setting.

The pieces

Piece Where it runs
web Vercel Functions, Bun runtime: pages, REST, MCP, LTI, webhooks received
worker, scheduler, collab, content A companion host: Fly, Railway, ECS, or your own Docker host with docker/compose.yaml
Postgres External, behind a transaction pooler: Neon, Supabase, or RDS with PgBouncer
Files S3 or R2. There is no persistent disk
Background jobs Enqueued with pg-boss inside the request’s transaction, run by the companion worker. With QUIRE_QUEUE_DRIVER=vercel on the companion worker, the light jobs (unordered notification and webhook deliveries) go through Vercel Queues (VERCEL_QUEUE_REGION, VERCEL_QUEUE_TOKEN), so the pooled Postgres is not polled for them
Recurring jobs The companion scheduler. There is no Vercel Cron route, because a cron that does work inside a function times out
Tracing OpenTelemetry to your collector, OTEL_EXPORTER_OTLP_ENDPOINT

What this target cannot do

The web tier checks these when it starts and refuses, naming every problem, rather than failing later at the first email that never sends:

  • No SMTP. Vercel blocks outbound SMTP. Set QUIRE_EMAIL_PROVIDER_CONFIG to an HTTP provider (Postmark, SES, Mailgun, SendGrid or Resend). QUIRE_SMTP_URL is refused.
  • No local disk. QUIRE_STORAGE_DRIVER=local is refused.
  • No shared memory between invocations. QUIRE_REALTIME_DRIVER=inprocess is refused. Server-sent events are capped at the function timeout; the client reconnects with a cursor, so no event is lost, but presence is unavailable.
  • Pinned tenant databases are limited to about eight, because each one is another connection pool in an environment that cannot share pools.

Deploying

  1. Create a Vercel project from the repository with Root Directory apps/web. apps/web/vercel.json sets the install and build commands (NITRO_PRESET=vercel) and serves /sw.js uncached.
  2. Set the environment variables. From docker/.env.example, at least: QUIRE_DEPLOY_TARGET=vercel, QUIRE_APP_ORIGIN, QUIRE_CONTENT_ORIGIN, QUIRE_PLATFORM_DOMAINS, QUIRE_SECRET_KEY, QUIRE_MASTER_KEY, DATABASE_URL (the pooler’s address), QUIRE_REPORT_DATABASE_URL, QUIRE_AUDIT_DATABASE_URL, QUIRE_DATABASE_ID, QUIRE_EMAIL_PROVIDER_CONFIG, QUIRE_MAIL_FROM, the storage settings, and QUIRE_COLLAB_URL and QUIRE_COLLAB_SIGNING_KEY pointing at the companion’s collab service. QUIRE_REPORT_DATABASE_URL belongs to the physical database named by DATABASE_URL. For each additional registered database, add its own quire_report connection URL to both web and worker environments, then register its environment variable name as env:NAME in the platform console. Analytics fails closed if that database’s report URL is missing.
  3. On the companion host, run docker/compose.yaml without the web service, with the same docker/.env:
    docker compose -f docker/compose.yaml up -d migrate init worker scheduler collab content
    Migrations run there, before each Vercel deployment is promoted.
  4. Deploy. On a refused configuration the function log starts with “The web tier did not start on vercel” and lists each setting to change.

Upgrading

Migrate from the companion host first, then promote the new Vercel deployment, then roll the companion’s workers: the order in upgrade.md. A Vercel instant rollback is a code rollback and is always safe within a release.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close