Vercel Deployment
This guide covers deploying the OpenReader app to Vercel with external Postgres, S3-compatible object storage, NATS JetStream, and a standalone compute worker such as Railway.
What works on Vercel
- Documents (PDF/EPUB/DOCX/TXT/MD) work with
POSTGRES_URL+ external S3 storage. - Background TTS generation, playback, previews, DOCX conversion, Whisper alignment, PDF layout parsing, and audiobook export run through an external compute worker service.
- Audiobook export downloads the worker-owned playback stream; there are no audiobook-specific serverless routes.
- For worker setup details and worker-specific env vars, see Compute Worker (NATS JetStream).
The published compute-worker image includes headless LibreOffice. DOCX conversion therefore works
with Vercel when the external worker is configured; Vercel itself does not run soffice.
1. Environment Variables
Recommended production setup (auth enabled, admin panel enabled):
# Infrastructure
POSTGRES_URL=postgres://...
USE_EMBEDDED_WEED_MINI=false
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
S3_BUCKET=...
S3_REGION=us-east-1
S3_PREFIX=openreader
# Optional (non-AWS S3-compatible providers):
# S3_INTERNAL_ENDPOINT=https://private-s3-endpoint.example
# S3_PUBLIC_ENDPOINT=https://s3.example
# S3_BROWSER_TRANSPORT=presigned
# S3_FORCE_PATH_STYLE=true
# Auth (required for the admin panel)
BASE_URL=https://your-app.vercel.app
AUTH_SECRET=...
BOOTSTRAP_ADMIN_EMAIL=owner@example.com # new database only; remove after setup
BOOTSTRAP_ADMIN_PASSWORD=... # unique 16+ character initial password; remove after rotation
CRON_SECRET=... # generate with: openssl rand -base64 32
# Heavy compute (required on Vercel in current releases)
COMPUTE_WORKER_URL=https://<railway-worker-domain>
# Optional when browsers need a different public worker URL for playback audio
# COMPUTE_WORKER_PUBLIC_URL=https://<railway-worker-domain>
COMPUTE_WORKER_TOKEN=...
COMPUTE_CREDENTIAL_BROKER_TOKEN=... # generate separately; set the same value on the worker
TTS_PLAYBACK_TOKEN_SECRET=... # generate with: openssl rand -base64 32; set the same value on the worker
# Logging (recommended for Vercel log ingestion)
LOG_FORMAT=json
LOG_LEVEL=info
# First-boot seed for the TTS shared provider (optional; manage in-app afterwards)
# API_KEY=your_replicate_key
# API_BASE only needed for OpenAI-compatible self-hosted providers
# API_MODEL_NAME=the-upstream-model-name
If you also run an external worker service (for example Railway), configure it with:
COMPUTE_CREDENTIAL_BROKER_URL=https://<your-app-domain>/api/internal/compute/tts-credentials- the matching
COMPUTE_CREDENTIAL_BROKER_TOKEN - the matching
COMPUTE_WORKER_TOKENandTTS_PLAYBACK_TOKEN_SECRET - its NATS connection and the same S3 storage configuration used by the app
LOG_FORMAT=jsonCOMPUTE_LOG_LEVEL=info
Do not set the app's AUTH_SECRET or POSTGRES_URL on the worker. Provider lookup and decryption remain app-owned.
Self-hosted provider URLs configured in the admin panel must be reachable from the worker runtime,
not from Vercel or the browser. A Railway worker cannot reach a provider through localhost or
host.docker.internal on your personal computer.
API_KEY / API_BASE / API_MODEL_NAME are one-shot bootstrap seeds on first deploy. API_MODEL_NAME sets the seeded provider's default model and defaults to kokoro. After boot, manage providers and site features in Settings → Admin. Changes there apply on refresh without a redeploy. See Admin Panel.
1a. Railway + Synadia quick start (worker mode)
If your Vercel app uses an external compute worker on Railway with Synadia Cloud (NGS):
- Deploy a Railway service from:
ghcr.io/richardr1126/openreader-compute-worker:latest
- Enable public networking on that Railway service and set:
COMPUTE_WORKER_URL=https://<railway-worker-domain>(in Vercel)COMPUTE_WORKER_PUBLIC_URL=https://<railway-worker-domain>(in Vercel) if browsers cannot reachCOMPUTE_WORKER_URLdirectly
- Use the same
COMPUTE_WORKER_TOKENvalue in both Vercel and Railway worker env vars. - Use the same
COMPUTE_CREDENTIAL_BROKER_TOKENvalue in both Vercel and Railway worker env vars, and point the worker broker URL at the Vercel app. - Use the same
TTS_PLAYBACK_TOKEN_SECRETvalue in both Vercel and Railway worker env vars. The worker derives a domain-separated private-text fingerprint key from it; no additional text-hash secret is configured.
For complete Railway worker env vars (NATS_*, S3_*, health checks, and Synadia .creds guidance), see Compute Worker (NATS JetStream).
2. First-run admin configuration (recommended)
On a fresh database, sign in with the seeded credential and change its password in Settings → Account to activate the administrator role. No second deploy is required. The initial password is then invalid; removing the bootstrap values from Vercel later is optional secret hygiene. Existing v4 administrators keep their roles during migration. Then open Settings → Admin and configure:
- Shared providers: create/edit your provider key(s) here (encrypted at rest).
- Site features:
enableDocxConversion=truewhen the published compute-worker image is connected.enableTtsProvidersTab=falseif you want shared-provider-only UX.signupPolicy=open,approval, orclosedaccording to your registration policy.defaultTtsProvider=replicate(or your preferred shared slug).showAllProviderModels=falseif you want users locked to each provider's default model.enableAudiobookExport=true.
3. Runtime JSON seed (optional)
If you must pre-seed site features, shared providers, or account email delivery at deploy time, use RUNTIME_SEED_JSON or RUNTIME_SEED_JSON_PATH (versioned JSON seed document). Keep the seed secret when it contains a Resend API key. Prefer the admin panel for ongoing management.
See Environment Variables for schema and examples.
Set both BASE_URL and AUTH_SECRET — they are required for startup, the admin panel, and
encrypting admin-stored TTS credentials. Keep AUTH_SECRET stable across deployments.
Admin-managed TTS provider keys are encrypted with a key derived from AUTH_SECRET. If you rotate AUTH_SECRET after the first deploy, you must re-enter each admin shared provider's API key from the UI.
For all variables and defaults, see Environment Variables.
4. Upgrade from v4.4 to v5
Vercel deployments do not run the @openreader/bootstrap process, so automatic startup migrations do not run there.
- Take the v4 app offline or pause deployment traffic, then back up Postgres and the S3 bucket.
- From a v5 source checkout, configure the production
POSTGRES_URLand runpnpm migrate. - In the same controlled environment, configure the production
S3_INTERNAL_ENDPOINT, bucket, region, access key, secret key, path-style setting, and prefix, then runpnpm migrate-decommissiononce. - Deploy the v5 worker and app with matching worker, credential-broker, playback, and S3 settings; configure the worker's NATS connection.
- Check the migration output, then perform the smoke test below before restoring normal traffic.
The decommission command idempotently deletes only the retired v4 object roots
tts_segments_v1/, tts_segments_v2/, and audiobooks_v1/. It does not remove documents,
accounts, settings, or v5 playback artifacts. Re-running it is safe if a deployment is interrupted.
For the full schema history and Docker's automatic path, see Migrations.
5. Scheduled maintenance tasks
The repository configures /api/admin/tasks/tick as a Vercel Cron route. Set CRON_SECRET; requests without the matching bearer token are rejected.
The checked-in Hobby-compatible schedule invokes the route once daily. The admin task panel therefore prevents selecting intervals shorter than one day on Vercel, even though self-hosted deployments can run tasks more frequently.
Each due task is claimed with a database-backed lease, due tasks start independently, and individual runs are aborted and marked failed after four minutes. Review failures and run tasks manually from Settings → Admin → Scheduled tasks.
6. Runtime expectations and caveats
- Playback and audiobook export require the external compute worker and S3-compatible object storage because generation continues outside the serverless request lifecycle.
- For production Vercel deploys, use
POSTGRES_URLinstead of SQLite.
7. Smoke test after deploy
- Upload and read a PDF/EPUB document.
- Confirm sync/blob fetch works across refreshes/devices.
- Start TTS playback and download an audiobook MP3 export.
- Verify worker-backed word highlighting and PDF parsing.
- Open Settings → Admin → Scheduled tasks, run one task manually, and confirm the next daily cron invocation succeeds.