Deploy on Vercel

Vercel can run the complete Doso web and API tier. The lowest-change path uses Vercel Services: Next.js for the web tier, the existing Rust API as an OCI container image, and a Marketplace Postgres database.

Use one Postgres cluster with separate databases or schemas, or separate clusters when stronger isolation is required. Only the web service receives public traffic. Its existing /v1/** route proxies to the API over the DOSO_API_PROXY_TARGET service binding.

Prerequisites#

  • A Vercel plan/account with Services and Container Images enabled.
  • A custom domain such as app.example.com.
  • Hosted Postgres with TLS.
  • A stable 32-byte settings encryption key.
  • Docker for local vercel dev container testing.

Database#

Vercel no longer operates a first-party Postgres product. Install a provider from the Vercel Marketplace:

  • Neon is the default recommendation. Its native integration injects a pooled DATABASE_URL, supports preview branches, and supplies a direct URL for migrations.
  • Supabase is also supported. Use the session pooler for the long-lived container path; transaction mode may require disabling prepared statements.
  • PlanetScale Postgres works through a normal TLS connection string, but the current Vercel PlanetScale Marketplace listing is MySQL-oriented. Create the Postgres database in PlanetScale and add its pooled URL manually.

Keep runtime and database in the same region. Use the pooled endpoint for application traffic and a direct endpoint for schema migrations.

Deploy the Services configuration#

The example vercel.json defines:

  • web: the Next.js application in web/;
  • api: the OCI image built by Dockerfile.api.vercel;
  • a private binding that injects the API URL as DOSO_API_PROXY_TARGET;
  • public routing to web only.

Run from the repository root:

1vercel link2vercel env add DATABASE_URL3vercel env add BETTER_AUTH_DATABASE_URL4vercel env add BETTER_AUTH_SECRET5vercel env add DOSO_SETTINGS_KEY_HEX6vercel deploy

For Git deployments, copy the example configuration to the repository root as vercel.json, or point your deployment tooling at it with the Vercel CLI local configuration option. Do not set Vercel's project Root Directory to this folder: both web/ and the Rust workspace must remain in the build context.

Environment#

Set these for Production, Preview, and Development as appropriate:

1DATABASE_URL=postgresql://...-pooler.../doso?sslmode=require2BETTER_AUTH_DATABASE_URL=postgresql://...-pooler.../auth?sslmode=require3BETTER_AUTH_SECRET=<random 32-byte secret>4DOSO_SETTINGS_KEY_HEX=<64 hex characters>5 6DOSO_AUTH_MODE=jwks7NEXT_PUBLIC_DOSO_AUTH_MODE=session8NEXT_PUBLIC_API_URL=9BETTER_AUTH_URL=https://app.example.com10DOSO_AUTH_ISSUER=https://app.example.com11DOSO_AUTH_AUDIENCE=https://app.example.com12DOSO_PUBLIC_API_URL=https://app.example.com13DOSO_JWKS_URL=https://app.example.com/api/auth/jwks14 15DOSO_POSTGRES_MAX_CONNECTIONS=316DOSO_HOSTED_RUNTIME=true

The service binding supplies DOSO_API_PROXY_TARGET; do not hardcode it.

Runtime limits#

Vercel Container Images run as Functions:

  • idle production instances scale down after five minutes;
  • containers receive SIGTERM with a 30-second shutdown grace period;
  • Rust/container requests are limited to 300 seconds on Hobby and 800 seconds on Pro/Enterprise;
  • request and response bodies are limited to 4.5 MiB;
  • instances scale horizontally automatically;
  • local files are ephemeral and cannot hold SQLite, the settings key, or the Git vault.

The existing API can serve normal requests and SSE from a container, but multiple replicas are not yet a safe production topology. Run events and live settings are process-local, startup recovery is not owner-aware, and detached ingestion can outlive an invocation. Large uploads should go directly to object storage and then be imported by URI.

Treat this recipe as a constrained deployment until Doso has durable jobs, run-owner leases, one-shot release migrations, and shared event delivery.

Native Rust Functions#

Vercel's Rust Runtime is a real beta option, but it is not the existing binary packaged differently. A native port must:

  1. extract the Axum router from process startup;
  2. wrap it with vercel_runtime and cache initialization per warm instance;
  3. remove listener and process-lifetime assumptions;
  4. move migrations and detached jobs out of handler startup;
  5. replace local executables/filesystem features such as Poppler and Git;
  6. solve the same upload, event, duration, and replica-ownership constraints.

Use the OCI image path today; evaluate native Rust Functions when smaller images and function-level isolation justify that application port.

SaaS#

Skip this. Multi-org is ../../saas.

Verify and operate#

  1. Run migrations against the direct database URL before promotion.
  2. Check /api/auth/jwks, sign-in, and an authenticated /v1/settings.
  3. Start a streamed answer, then verify reconnect behavior.
  4. Replace the API instance and confirm settings remain decryptable.
  5. Use Vercel Runtime Logs and Observability for container stdout/stderr.
  6. Promote a validated preview rather than rebuilding production.

Official references: Container Images, Services, Service bindings, Function limits, and Postgres on Vercel.