Configuration
The environment variables the server reads: the required three, then sync, batching, storage, email, billing and scale.
Everything is read from the environment at startup. Only three variables are required. The rest have defaults that suit a single-instance deployment.
Required
| Variable | Default | What it is |
|---|---|---|
DATABASE_URL | dev-only local value | Postgres connection string. |
JWT_SECRET | dev-only insecure default | Signs sessions and sync tokens. Generate one with openssl rand -base64 32. Rotating it invalidates every session and sync token. |
BETTER_AUTH_URL | http://localhost:3010 | The public base URL used in auth links such as verification and invitations. It must match the URL clients actually use. |
Serving and sync
| Variable | Default | What it is |
|---|---|---|
PORT | 3010 | HTTP API port, which serves both WebSocket paths as well. |
HOCUSPOCUS_PORT | 3011 | Legacy dedicated editing-socket port. |
VAULT_SYNC_PATH | /vault-sync | Path of the background vault channel on PORT. |
CORS_ORIGINS | unset | Comma-separated origins allowed to call the API. |
SYNC_TOKEN_TTL_SECONDS | 600 | Lifetime of a per-document sync token. |
COMPACTION_THRESHOLD | 50 | Pending CRDT updates before the server compacts them into a snapshot. |
PG_POOL_MAX | 30 | Connections this instance pools. Keep the total across instances under the database's own limit, and never below BACKFILL_CONCURRENCY. |
Batching and first sync
These bound how much one request may carry. The desktop app chunks to them, so raising them is rarely necessary and lowering them is a safe way to protect a small instance.
| Variable | Default | What it is |
|---|---|---|
BATCH_MAX_NOTES | 200 | Notes per batch request. Past it the request is refused as too large. |
BATCH_MAX_FOLDERS | 500 | Folders per batch. Higher because a folder row is cheaper. |
BATCH_MAX_FILES | 200 | Files per batch. |
BATCH_MAX_DOCS | 100 | CRDT content items per batch. |
BATCH_MAX_DECODED_BYTES | 4 MiB | Total decoded update bytes one content batch may carry. This is the heap bound behind the request body limit. |
BOOTSTRAP_MAX_PAGE_BYTES | 4 MiB | Byte budget for one bootstrap page. A single document larger than a page ships alone rather than being refused. |
BOOTSTRAP_MAX_PAGE_DOCS | 256 | Document budget for one bootstrap page. |
BOOTSTRAP_CONCURRENCY | 4 | Bootstrap pages built at once per instance. Past it a request answers 503 with a Retry-After. Raise it only alongside the container's memory. |
BOOTSTRAP_TTL_HOURS | 24 | How long a bootstrap session stays valid before the client must start again. |
BACKFILL_CONCURRENCY | 6 | Documents streamed at once to a freshly connected vault subscriber. |
Attachments
Summarised here, with the full behaviour on the attachment storage page.
| Variable | Default | What it is |
|---|---|---|
BLOB_STORAGE | postgres | postgres or s3. An unrecognised value, or s3 with an incomplete bucket configuration, is a fatal startup error. |
MAX_BLOB_BYTES | 25 MB | Ceiling for one attachment on Postgres. |
MAX_BLOB_BYTES_DIRECT | 500 MB | Ceiling for one attachment on S3. |
MAX_INFLIGHT_UPLOAD_BYTES | 2x MAX_BLOB_BYTES | Upload bytes admitted at once. Beyond it uploads queue, then shed with a 503. |
BLOB_MIME_ENFORCE | reject | reject answers 415 for an unknown content type. warn logs and stores it. |
BLOB_PENDING_TTL_MINUTES | 60 | How long an abandoned upload holds its dedupe slot. |
BLOB_GC_ENABLED | false | Whether unreferenced attachments are collected. |
Optional, and off unless you configure it. See the email page for what each transport needs.
| Variable | Default | What it is |
|---|---|---|
EMAIL_FROM | unset | Sender address. With this and one transport, password reset, sign-up verification and invitation emails switch on. |
SMTP_URL | unset | Any SMTP server, with STARTTLS or TLS. |
RESEND_API_KEY | unset | The HTTPS alternative to SMTP. |
EMAIL_TRANSPORT | inferred | Force a transport instead of inferring it. log and memory exist for development and are refused in production. |
Billing
Leave every billing variable unset and billing is fully disabled: no upgrade interface in the clients, no free-tier limits, and every self-hosted vault unlimited. Setting them turns on subscriptions through Polar.
| Variable | Default | What it is |
|---|---|---|
POLAR_ACCESS_TOKEN | unset | Turns billing on. Unset means disabled. |
POLAR_WEBHOOK_SECRET | unset | Signing secret of the webhook endpoint pointed at /api/billing/webhook. |
BILLING_MODEL | vault | vault for per-vault plans with the free caps, or team for one account sold per seat. |
TEAM_PRICE_MONTHLY_CENTS / TEAM_PRICE_YEARLY_CENTS | 1000 / 11000 | Per-seat prices shown in the app. Polar's product price is what is actually charged. |
TEAM_MIN_SEATS | 3 | Smallest seat count a checkout accepts. |
FREE_MAX_VAULTS / FREE_MAX_MEMBERS / FREE_MAX_STORAGE_MB | 2 / 3 / 1024 | Per-vault model only. Free-tier caps, enforced only when billing is on. |
ABUSE_MAX_NOTES / ABUSE_MAX_STORAGE_MB | 100000 / 10240 | Team model only. Per-vault ceilings for free accounts. Never applied with billing off. |
Other
| Variable | Default | What it is |
|---|---|---|
REDIS_URL | unset | Unset means a single instance with in-memory fanout. Set it to run several instances. See scaling. |
BAALDA_DEPLOYMENT | cloud | self-hosted lets people use their own model keys for server-side AI without any billing setup. cloud requires a paid plan for it. |
DEEP_LINK_SCHEME | baalda | The app scheme the server's pages bounce into. Set baalda-staging on a staging server so links open the right build. |
OPENAI_API_KEY | unset | Optional embeddings for semantic search. The server works fully offline without it. |
This page is written from docs/DEPLOY.md in the repository. Those documents go further, and they are the ones that change first.
Read it on GitHubWas this page helpful?
