Running the stack
The core local dev loop: bring up MongoDB and Redis, run the Backend, run the Console, and run a single voice-fleet worker — then watch a real call land on your machine.
Once the repos are cloned and dependencies are installed, you bring the platform up one service at a time, in order. Each service depends on the one before it: the Backend needs the data layer, the Console needs the Backend, and a fleet worker needs the Backend's config URL. Follow the four steps below and you'll have a full local loop that can run an actual call.
This page assumes you've already done local setup — cloned the four repos, installed uv and Node, and run uv sync / npm install. If you haven't, start there.
The local loop at a glance
The arrow that matters most is Fleet → Backend: a worker holds no durable state and fetches everything it needs for a call from the Backend's config URL at the moment the call starts. Get that one wire right and a call will run.
Start MongoDB and Redis
The Backend needs both before it will boot. The quickest way on a laptop is two
Docker containers — MongoDB on its default 27017 and Redis on 6379, exactly
where the Backend's .env expects them.
docker run -d --name ori-mongo -p 27017:27017 mongo:7
docker run -d --name ori-redis -p 6379:6379 redis:7These map straight onto the Backend defaults: MONGODB_URI=mongodb://localhost:27017
and REDIS_URL=redis://localhost:6379. Leave them running in the background.
Tip
Already have MongoDB or Redis installed natively? Skip the containers — anything
listening on those two ports works. The Backend only cares about the URIs in its .env.
Run the Backend
From the vox-backend repo, copy .env.example to .env and set at least a
JWT_SECRET. The defaults already point at your local MongoDB and Redis.
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB=voxbridge
REDIS_URL=redis://localhost:6379
JWT_SECRET=dev-secret-change-me
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=1440
LOG_LEVEL=INFOStart the API on port 8080 via the run script (which wraps uvicorn):
./scripts/run.shuv run uvicorn voxbridge.app:app --host 0.0.0.0 --port 8080 --app-dir srcThe app only boots when its required settings are present, so a clean start confirms MongoDB and Redis are reachable. With a fresh database, seed the first admin user so you can log into the Console:
uv run python scripts/seed_admin.pyMONGODB_DB is voxbridge and the Dialler shares the exact same database. On a
laptop you usually don't run the Dialler at all (see Advanced) — but if you do, point it at this same DB.
Run the Console
From the vox-frontend repo, make sure VITE_API_URL points at your local
Backend. For Ori the brand .env is committed in the repo; for local work the
only value you need to override is the API URL:
VITE_API_URL=http://localhost:8080Start the Vite dev server:
npm run devVite serves the SPA (default http://localhost:5173) and every admin call it
makes goes to VITE_API_URL. If logins fail with network errors, this is almost
always the value to check.
Run one voice-fleet worker
From the vox-agents repo, copy .env.example to .env. The single most
important line is the config URL pointing back at your Backend, plus the provider
keys for the speech stack and somewhere to put recordings:
VOXBRIDGE_CONFIG_URL=http://localhost:8080/api/v1/config
# Speech providers — fill in the ones your test bot uses
DEEPGRAM_API_KEY=...
SONIOX_API_KEY=...
OPENAI_API_KEY=...
GOOGLE_API_KEY=...
ELEVENLABS_API_KEY=...
SARVAM_API_KEY=...
# Recordings — local MinIO (or any S3-compatible storage)
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=...
MINIO_SECRET_KEY=...
MINIO_BUCKET=recordings
MINIO_SECURE=false
AUDIO_SAMPLE_RATE=16000
MAX_CONCURRENT_CALLS=30
LOG_LEVEL=INFOFor local development you want one worker on a TCP port (not the production Unix-socket model). The dev run script does multiple workers by default; for a single, easy-to-watch worker, run uvicorn directly:
uv run uvicorn voxcore.app:app --host 0.0.0.0 --port 8000 --workers 1 --app-dir srcVOXCORE_WORKERS=1 ./scripts/run.shRecordings need real object storage. If you don't run a local MinIO, the call
itself will still work but the recording upload step will fail. Either start a
MinIO container exposing localhost:9000 with a recordings bucket, or point the
MINIO_* vars at any S3-compatible endpoint you can reach.
How config flows to the worker
A fleet worker is stateless by design. When a call begins, the worker calls the
Backend at VOXBRIDGE_CONFIG_URL to fetch the runtime config for that bot —
prompts, voice settings, the tool set, and any CRM data for the contact — and runs
the conversation entirely from what comes back. Nothing about the bot lives on the
worker between calls.
This is why the order of the four steps matters: the Backend must be up and
reachable at the config URL before a worker can do anything useful. If a local call
errors immediately, check that VOXBRIDGE_CONFIG_URL resolves and that the bot you're
testing exists in the Backend's database.
Verify it works
A quick three-point check confirms the whole loop is wired up:
Console loads and logs in
Open the Vite URL (default http://localhost:5173), and log in with the admin
user you seeded in step 2. If the dashboard loads, the Console → Backend wire
(VITE_API_URL) is good.
The Backend is healthy
curl http://localhost:8080/healthA healthy response means MongoDB and Redis are reachable and the API booted with its required settings.
The worker is healthy
curl http://localhost:8000/health/health reports a single worker's status. (In production you'd also hit
/health/fleet to aggregate every worker over its socket — but with one local
worker on a TCP port, /health is all you need.)
Tip
With all three green, the easiest way to actually exercise a call locally is the
in-browser widget (POST /livekit/widget) or a direct WebSocket test against
/ws/{bot_id} — no carrier, no phone number, no SIP. Create a bot in the Console
first so the worker has a config to fetch.
Advanced: LiveKit and the Dialler
The two services below complete the platform but need real telephony to be useful, so they're rarely run on a laptop. Reach for them only when you're testing SIP or campaign behaviour specifically.
LiveKit handles SIP trunks and the media plane for inbound DIDs, outbound dial,
and the browser widget. It runs as a Docker Compose stack (livekit +
livekit-sip), brought up by the helper in the vox-agents repo:
./infra/livekit/setup.shFor real phone calls you also need carrier SIP trunks configured and, on a cloud
host, use_external_ip plus a webhook for inbound dispatch. None of that is
needed for the local widget/WebSocket path above — which is exactly why that path
is the easy way to test a call on your machine.
The Dialler is a background asyncio worker (with a small health server) that paces
outbound campaign calls. From the vox-dialler repo:
uv run python -m voxdialler.mainIts health and metrics live on port 8090:
curl http://localhost:8090/health
curl http://localhost:8090/metricsThe Dialler only does something with running campaigns, live carrier trunks, and a reachable fleet — so it's not part of the normal local loop. There is also a hard rule: exactly one Dialler instance per database. Two diallers on the same MongoDB both pace as if they're alone and will over-dial. If you must run it locally, make sure no other instance is pointed at the same DB.
Where to go next
Local setup
Prerequisites, cloning the four repos, and installing dependencies — the step before this one.
Repository map
The four repositories, their boundaries, and the contracts between them.
Deploy it
The Bitbucket → Jenkins pipeline, host deployment, and the production run model.
System architecture
How the four services, the data layer, and the telephony plane fit together.
Local setup
Get a developer machine ready: install the toolchain, clone the four repos, bring up MongoDB and Redis locally, then do the first-time install for each service.
Configuration reference
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.