Baselyra Docs

Configuration

All runtime configuration comes from the environment; nothing is read from disk at runtime, so the same image runs anywhere with a different .env. Two variables are required — DATABASE_URL and JWT_SECRET — and the process refuses to start without them.

Required

VariableNotes
DATABASE_URLThe project database: your tables, auth, storage, and this project's Baselyra configuration. This is what /rest/v1 exposes.
JWT_SECRETHS256 secret. Signs access tokens, the anon key, the service key, Studio tokens and signed storage URLs. At least 32 random bytes; scripts/setup.sh generates one.

Instance

VariableDefaultEffect
NODE_ENVproductionAnything but production turns per-request logging on.
PORT3000Port inside the container. Compose maps it with BASELYRA_PORT.
HOST0.0.0.0Bind address.
BASELYRA_PUBLIC_URLhttp://localhost:3000This instance's public origin. Goes into email links, signed storage URLs and the OAuth callback URL. A trailing slash is stripped.
BASELYRA_SITE_URLhttp://localhost:3000Your frontend. Where GET /auth/v1/verify redirects, and the default OAuth redirect target. Always this value, never anything from the request — which is what keeps those routes from being open redirects.
BASELYRA_VERSION0.1.0Reported by /health. The Docker build sets it.
CORS_ORIGINS*Comma-separated allowed origins. * means any page on the internet may call the API with a token the browser holds. Name your origins in production.
LOG_LEVELinfoFastify log level. authorization, apikey and cookie headers are redacted at every level.

Databases

VariableDefaultEffect
CONTROL_DATABASE_URLDATABASE_URL with the database name swapped for baselyra_controlThe control database: Studio accounts, audit log, import history, request metering, the project registry. Set it only to put the control database on another server. Pointing it at the same database as DATABASE_URL is refused at boot.
DATABASE_POOL_MAX12Connections per pool, per app process. Stay well under Postgres' max_connections.
DATABASE_STATEMENT_TIMEOUT_MS15000Kills a runaway API query. Exceeding it is a 408 statement_timeout. Bulk import copies raise it to unlimited for the duration of a table.

Replica settings

Four variables exist and are read, and the registry will open a pool per URL and probe it every DATABASE_REPLICA_HEALTH_MS with pg_is_in_recovery() and pg_last_xact_replay_timestamp().

VariableDefault
DATABASE_REPLICA_URLSempty — comma separated
DATABASE_REPLICA_MAX_LAG_MS5000
DATABASE_REPLICA_HEALTH_MS10000
DATABASE_READ_AFTER_WRITE_MS10000

Project settings

Every lookup of "which database is this project" reads control.projects rather than a constant, so a switcher later is a change to one resolver. Today that table holds a single row and nothing creates a second In progress.

VariableDefaultEffect
PROJECT_DEFAULT_SLUGdefaultThe project a request that names none is answered from.
PROJECT_DATABASE_PREFIXbaselyra_Prefix for a created project's database name.
PROJECT_MAX_POOLS8Idle project pools held open at once; past this the least recently used is closed.

Tokens and sessions

VariableDefaultEffect
JWT_ACCESS_TTL3600Access token lifetime in seconds. An access token cannot be revoked before it expires, so keep this short — that is what the refresh token is for.
JWT_REFRESH_TTL2592000Refresh token lifetime in seconds (30 days).
JWT_ISSUERbaselyraThe iss claim.

Auth behaviour

VariableDefaultEffect
AUTH_CONFIRM_EMAILtrueRequire a confirmed address before the first sign-in. With it on, POST /auth/v1/signup answers with session: null.
AUTH_ALLOW_SIGNUPStrueWhen false, /auth/v1/signup answers 403, third-party sign-in refuses to create a new account, and a phone code is only sent to a number that already has one.
AUTH_MIN_PASSWORD_LENGTH8The floor. There is no composition rule — length is the property that matters. The ceiling is 256 bytes, because scrypt is linear in input length.
AUTH_MAX_ATTEMPTS8Failed sign-ins per email+IP inside the window before a 429 with Retry-After.
AUTH_ATTEMPT_WINDOW900That window, in seconds. Nothing is locked permanently, so nobody can lock a competitor out of their own account.
AUTH_OTP_TTL600One-time code lifetime in seconds, for both email and phone codes.

Third-party sign-in

A provider is offered only when both halves are present. Half-configured is indistinguishable from not configured, and a sign-in button that cannot work is worse than no button.

BASELYRA_OAUTH_GOOGLE_CLIENT_ID=…
BASELYRA_OAUTH_GOOGLE_CLIENT_SECRET=…
BASELYRA_OAUTH_GITHUB_CLIENT_ID=…
BASELYRA_OAUTH_GITHUB_CLIENT_SECRET=…

The provider id is uppercased into the variable name: google, github, linkedin, facebook, instagram, tiktok, envato.

VariableDefaultEffect
BASELYRA_OAUTH_REDIRECT_ALLOWLISTemptyExtra targets a sign-in may return the browser to, on top of BASELYRA_SITE_URL. Comma-separated absolute http(s) URLs; a path on one narrows the allowance to that path and below.

Full flow, callback URL and identity linking rules: Third-party sign-in.

Storage

VariableDefaultEffect
STORAGE_ROOT/var/lib/baselyra/storageOne directory per bucket. A Docker volume in the shipped compose file. Back it up or lose it.
STORAGE_MAX_FILE_BYTES52428800Global upload cap (50 MB), and the Fastify body limit. A bucket's own file_size_limit can be lower. Keep your proxy's body limit above this.

Email

VariableDefaultEffect
SMTP_HOSTemptyEmpty means nothing is sent — the message, including confirmation and recovery links, is printed to the app log. Right for a laptop, never right in production.
SMTP_PORT587
SMTP_SECUREfalsetrue only for implicit TLS on port 465.
SMTP_USER / SMTP_PASSempty
SMTP_FROMBaselyra <no-reply@localhost>

Phone and SMS

With SMS_PROVIDER unset the code is printed to the log instead of sent, exactly as email is. A configured provider that refuses a message still throws.

VariableDefaultEffect
SMS_PROVIDERemptyOne of twilio, vonage, messagebird, sns, plivo, webhook.
SMS_DEFAULT_COUNTRYemptyCountry calling code for a number typed without one, e.g. 1 or 44. Unset means a number must arrive in international form to be accepted at all.
SMS_OTP_COOLDOWN60Seconds before the same number may be sent another code.
SMS_MAX_PER_NUMBER_PER_HOUR5Hard cap per number per hour. Every message is a charge on your account and the endpoint that causes one is unauthenticated, so this ceiling is what stops a stranger spending your money.
SMS_FROMemptySender id or number, where the provider needs one.

Per-provider credentials — TWILIO_ACCOUNT_SID, VONAGE_API_KEY, PLIVO_AUTH_ID and the rest — are listed on Phone one-time codes.

Realtime

VariableDefaultEffect
REALTIME_MAX_CHANNELS100Channels one socket may hold.
REALTIME_HEARTBEAT_MS30000Server ping interval. A socket that misses two consecutive pongs is terminated.

AI

Entirely optional. Without a key every /ai/v1 route answers 503 ai_disabled, the Studio hides its AI panels, and nothing else changes.

VariableDefaultEffect
DEEPSEEK_API_KEYemptyThe key stays on the server and never appears in a response.
DEEPSEEK_BASE_URLhttps://api.deepseek.comAny OpenAI-compatible /chat/completions endpoint, so a local Ollama or vLLM behind an OpenAI shim works.
DEEPSEEK_MODELdeepseek-chat
DEEPSEEK_MAX_TOKENS2048Hard ceiling per completion. A caller may ask for fewer, never more.

Bootstrap and compose-only

VariableRead byEffect
BASELYRA_ADMIN_EMAILscripts/migrate.jsCreates the first Studio account in control.platform_users — never in your project's auth.users.
BASELYRA_ADMIN_PASSWORDscripts/migrate.jsIts password on first boot only. A re-run promotes the account to owner but never overwrites the password.
BASELYRA_PORTdocker-compose.ymlHost port, bound to 127.0.0.1. Default 3130.
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DBdocker-compose.ymlCompose composes DATABASE_URL from them.

A production .env

# instance
BASELYRA_PUBLIC_URL=https://api.example.com
BASELYRA_SITE_URL=https://app.example.com
BASELYRA_PORT=3130
CORS_ORIGINS=https://app.example.com
LOG_LEVEL=info

# secrets — generated by ./scripts/setup.sh
JWT_SECRET=…48 random bytes…
POSTGRES_USER=baselyra
POSTGRES_PASSWORD=…32 random bytes…
POSTGRES_DB=baselyra

# auth
AUTH_CONFIRM_EMAIL=true
AUTH_ALLOW_SIGNUPS=true
AUTH_MIN_PASSWORD_LENGTH=8

# email — without this, recovery links land in your log file
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=apikey
SMTP_PASS=…
SMTP_FROM="Acme <no-reply@acme.com>"

# storage
STORAGE_ROOT=/var/lib/baselyra/storage
STORAGE_MAX_FILE_BYTES=52428800

# cleared after the first sign-in
BASELYRA_ADMIN_EMAIL=
BASELYRA_ADMIN_PASSWORD=

Changing a value

$EDITOR .env
docker compose up -d          # recreates the app container with the new environment

Nothing is read from disk at runtime, so every change needs a restart. Changing POSTGRES_PASSWORD also means changing it in Postgres (alter role baselyra password '…') and restarting both containers.

Edit this page Report a problem

Esc
navigate open Esc close