Every environment variable for the four Ori services — Backend, Voice fleet, Dialler, and Console — grouped by service, with meaning, default, and which ones are required to boot.
Every Ori service reads its configuration from a .env file. Each repository ships
a .env.example you copy to .env and fill in — nothing is hard-coded, so the same
build runs locally, on staging, and in production by swapping the file.This page is the complete inventory: every variable, what it means, a sensible
default, and whether the service refuses to boot without it. It also calls out the
handful of variables that wire the services to each other — get those wrong and the
pieces won’t find one another.
Three of these .env files are runtime configuration — the Python services
(Backend, Voice fleet, Dialler) read them every time they start, so a change takes
effect on the next systemctl restart. The Console is different: its VITE_*
variables are build-time and get baked into the static bundle. Changing a
Console variable means rebuilding, not restarting. See Console (build-time vars).
Most variables are local to one service. A small set is shared or cross-referenced —
these are the ones to set deliberately and keep consistent across hosts.
Variable
Set in
Points at / shared with
What breaks if wrong
VOXBRIDGE_CONFIG_URL
Voice fleet
The Backend’s config endpoint
The fleet can’t fetch per-call runtime config; every call fails to start.
FLEET_URLS
Dialler
The Voice fleet host(s)
The dialler can’t attach answered calls to a worker.
VOXCORE_SECRET
Dialler and Voice fleet
Shared secret on both sides
Mismatch → the fleet rejects the dialler’s /attach (and config-URL passthrough).
MONGODB_DB
Backend and Dialler
Same MongoDB database (voxbridge)
Different values → dialler paces against an empty/foreign campaign set.
VITE_API_URL
Console
The Backend API base URL
The dashboard can’t reach the Backend; every API call fails.
MAX_CONCURRENT_CALLS
Voice fleet
Per-worker call slot count
Set >1 in production and a worker takes a second call it can’t serve.
The cleanest way to reason about it: the fleet dials out to the Backend
(VOXBRIDGE_CONFIG_URL), the dialler dials out to the fleet (FLEET_URLS),
the Console dials out to the Backend (VITE_API_URL), and VOXCORE_SECRET
is the shared password on the dialler→fleet hop. Everything else is internal to a
single service.
Never commit a real .env. Secrets — JWT_SECRET, every provider API key,
VOXCORE_SECRET, LIVEKIT_API_SECRET, MinIO and Mongo credentials — belong in the
.env on each host (or your secret store), not in version control. Commit only the
.env.example with placeholder values. JWT_SECRET in particular signs every
operator session: leaking it lets anyone mint a valid admin token, and rotating it
logs every operator out. Generate it with something like openssl rand -hex 32 and
keep it per-environment.
The Voice fleet runs the call workers. In production each worker is a single-worker
uvicorn process on its own Unix socket behind nginx; in dev it’s a single
multi-worker process on port 8000. It needs a reachable Backend config URL,
object storage for recordings, and LiveKit for SIP/widget calls. Twenty-five
variables.
These are the speech-to-text, language-model, and text-to-speech credentials. Set
the keys for the providers your bots actually use — a bot configured for a provider
whose key is missing will fail when that stage runs.
Calls a single worker will serve — 1 in production
30 (example) / 1 (prod)
Yes
AUDIO_SAMPLE_RATE
Pipeline sample rate in Hz
16000
No
LOG_LEVEL
Log verbosity
INFO
No
MAX_CONCURRENT_CALLS is per worker, and is 1 in production. The fleet’s
scaling model is one call per worker process, enforced by nginx max_conns=1. The
.env.example ships 30 for convenience when running a single dev process, but on a
real fleet host each voxcore@<i> worker must keep it at 1 — otherwise nginx hands
a worker a second call it has no slot for. You scale capacity by adding workers, not
by raising this number.
VOXCORE_SOCKET (/tmp/voxcore_<i>.sock) is not an .env value — it’s supplied
per worker by the systemd template unit voxcore@.service, so worker N listens on
/tmp/voxcore_N.sock. See the production deployment docs for the socket model.
The Dialler is a background asyncio worker (with a small health server), not a web
app. It shares MongoDB with the Backend, attaches answered calls to the fleet, and
dials out through LiveKit SIP. Eighteen variables.
Run exactly one Dialler per database. Two diallers against the same Mongo each
pace as if they’re alone and over-dial. Place the single instance on the SIP /
LiveKit host. This is a placement rule, not a config flag — there’s no setting that
makes two instances safe.
FLEET_URLS is a JSON array string, not a comma-separated list. A single fleet
is ["https://fleet.example.com"]; multiple fleets are
["https://fleet-1.example.com","https://fleet-2.example.com"]. The dialler picks a
free worker across all of them when attaching an answered call.
The Console is a React + Vite single-page app. Its variables are build-time: Vite
reads the VITE_* values during npm run build and bakes them into the static
dist/ bundle that nginx serves. There is no runtime .env on the web host — to
change any of these you rebuild and redeploy the bundle, you don’t restart a service.For Ori’s single-brand repo the brand .env is committed at the brand path and the
build copies it to the root .env, so the build is just npm install && npm run build.
# vox-frontend brand .env (committed at the brand path, copied to root .env at build)VITE_API_URL=https://api.example.comVITE_TOKEN_KEY=ori_tokenVITE_BRAND_NAME=OriVITE_BRAND_POWERED_BY=OriVITE_BRAND_LOGO=/logo.pngVITE_PRIMARY_COLOR=#0F9B8EVITE_PRIMARY_SOFT=#78DFC5VITE_BG_COLOR=#0A2540VITE_AURORA_COLOR1=#0F9B8EVITE_AURORA_COLOR2=#78DFC5VITE_WIDGET_GLOBAL_NAME=OriWidgetVITE_CALLS_ONLY_DOMAINS=VITE_BRAND_THEME=ori
Because VITE_* values are compiled into the shipped bundle, treat them as
public. Never put a secret in a VITE_* variable — anything prefixed VITE_ is
visible in the browser. The Backend’s VITE_API_URL is fine to expose; API keys and
JWT_SECRET are not, and they live on the Backend, never here.