Pre-requisites
Before proceeding, confirm the following on the target host:- Docker — Docker Engine 24 or later and Compose v2 (
docker compose). On macOS, Colima or Docker Desktop is supported. - Node.js — Node 20 or later and pnpm, used to deploy Convex functions from the repository.
- Network — The default ports are available:
3210and3211(Convex),6791(dashboard),8080(web),3001(voice), and80/443(Caddy). Adjust*_PORTin.env.self-hostedif needed. - Storage — Persistent data is stored in the
convex_datavolume. Plan backup and retention separately.
Steps to deploy LobbyStack using Docker Compose
Run all commands from the repository root unless noted otherwise.1. Clone the repository
2. Configure environment variables
Copy the environment template and generate required secrets:.env.self-hosted before starting containers.
Initial installation (localhost) — For a first-run smoke test, keep the default *_PORT values and set URLs to localhost, for example:
| Variable | Example value |
|---|---|
CONVEX_SELF_HOSTED_URL | http://127.0.0.1:3210 |
APP_BASE_URL, SITE_URL | http://127.0.0.1:8080 |
VOICE_GATEWAY_BASE_URL | http://127.0.0.1:3001 |
VITE_WEB_CALL_ENDPOINT | http://127.0.0.1:3001/web-call/sessions |
CONVEX_URL | http://127.0.0.1:3210 |
CONVEX_SITE_URL | http://127.0.0.1:3211 |
CONVEX_CLOUD_ORIGIN, CONVEX_SITE_ORIGIN | Match the Convex URLs above |
WEB_CALL_ALLOWED_ORIGINS | http://127.0.0.1:8080 (must match the browser web URL) |
DASHBOARD_TEST_CALL_TOKEN | Optional shared Convex/voice-gateway token for elevated dashboard test-call limits from verified internal paths; localhost smoke tests do not require it. |
APP_HOSTNAME, VOICE_HOSTNAME, CONVEX_HOSTNAME, CONVEX_SITE_HOSTNAME | localhost for each (prevents Caddy from requesting certificates for *.example.com during smoke) |
CONVEX_URL, CONVEX_SITE_URL, and VITE_* at build time. Rebuild the web service after changing those values.
Application secrets must be present in the Convex deployment environment. Use pnpm self-hosted:convex:env (step 4) to sync values from .env.self-hosted; setting variables only on Compose services is insufficient for Convex functions.
The voice gateway reaches Convex over the internal Docker URL http://convex-backend:3211. Keep CONVEX_SITE_URL in .env.self-hosted as the public or browser-facing URL for Convex HTTP actions.
Refer to Environment variables for a full variable reference.
3. Start the Convex backend
Start the self-hosted Convex backend and dashboard. Compose pulls images on first run.CONVEX_SELF_HOSTED_ADMIN_KEY in .env.self-hosted.
The dashboard listens on 127.0.0.1 at port 6791 by default (http://127.0.0.1:6791). Authenticate with the admin key.
4. Deploy Convex functions
From the repository root, sync environment variables and deploy LobbyStack functions and components:CONVEX_SELF_HOSTED_URL and CONVEX_SELF_HOSTED_ADMIN_KEY from .env.self-hosted. If you use Convex Cloud for local development, keep .env.local in place; the helper scripts isolate it automatically.
Optional custom env file:
5. Start the application services
Build and start the web dashboard, voice gateway, and Caddy reverse proxy:docker/caddy/Caddyfile and baked into the image at build time. Rebuild the caddy service after changing hostnames or routes.
6. Verify the installation
WEB_CALL_ALLOWED_ORIGINS, Convex backend origin alignment (CONVEX_CLOUD_ORIGIN / CONVEX_SITE_ORIGIN), voice-gateway Convex connectivity (/health/convex), Convex /version, dashboard reachability, and the /voice/context HTTP action. A 404 from /voice/context is expected until tenant data exists.
To confirm the web service manually:
Helper scripts
| Command | Description |
|---|---|
pnpm self-hosted:secrets -- --write .env.self-hosted | Generate SESSION_ENCRYPTION_KEY, INTERNAL_SERVICE_TOKEN, INSTANCE_SECRET, and JWT material. |
pnpm self-hosted:secrets -- --write .env.self-hosted --force | Rotate secrets in an existing env file. Requires service restarts and pnpm self-hosted:convex:env. |
pnpm self-hosted:convex:env | Push variables from .env.self-hosted into the Convex deployment. |
pnpm self-hosted:convex:deploy | Deploy Convex functions and components. |
pnpm self-hosted:verify | Run HTTP smoke checks against localhost ports. |
SELF_HOSTED_WEB_VERIFY_URL, SELF_HOSTED_VOICE_VERIFY_URL, SELF_HOSTED_CONVEX_VERIFY_URL, SELF_HOSTED_CONVEX_SITE_VERIFY_URL, and SELF_HOSTED_DASHBOARD_VERIFY_URL when using non-default ports.
Additional steps
Complete these steps before exposing the deployment to production traffic.Public hostnames and HTTPS
Point DNS for the following hostnames to your server. Update.env.self-hosted with matching https:// URLs, then rebuild the web image, rebuild caddy if hostnames change, and run pnpm self-hosted:convex:env.
| Variable | Service |
|---|---|
APP_HOSTNAME | Web dashboard |
VOICE_HOSTNAME | Voice gateway |
CONVEX_HOSTNAME | Convex client API |
CONVEX_SITE_HOSTNAME | Convex HTTP actions |
Webhooks
Twilio voice (per business number):Production checklist
- Configure DNS for all public hostnames.
- Pin
CONVEX_BACKEND_IMAGE_TAGandCONVEX_DASHBOARD_IMAGE_TAGafter your first successful deploy. - Set production
https://URLs and origins in.env.self-hosted; rebuildwebandcaddyas needed. - Set
CADDY_BIND_ADDRESS=0.0.0.0so Caddy listens on public interfaces. - Set
WEB_CALL_ALLOWED_ORIGINSto your public app URL(s); keep them aligned withAPP_BASE_URL. - Run
pnpm self-hosted:convex:envafter any URL or secret change. - Configure Twilio voice and SMS webhooks.
- Add provider API keys (Twilio, OpenAI, and optional integrations); run
pnpm self-hosted:convex:envagain. - Validate signup, login, onboarding, and an inbound test call; confirm transcripts or recordings in the dashboard.
- Restrict access to the Convex dashboard (
127.0.0.1:6791); do not expose it publicly without additional access controls.
Troubleshooting
pnpm self-hosted:secrets fails — Run step 1 from the repository root after pnpm install.
Web dashboard cannot reach Convex — Rebuild the web service with the correct CONVEX_URL and CONVEX_SITE_URL.
CONVEX_DEPLOYMENT must not be set — Use pnpm self-hosted:convex:env and pnpm self-hosted:convex:deploy instead of pnpm exec convex when .env.local targets Convex Cloud. If a command was interrupted, restore .env.local from .env.local.self-hosted-bak or remove .env.local.self-hosted-lock.
Compose refuses to start without secrets — Run pnpm self-hosted:secrets -- --write .env.self-hosted before docker compose up. Compose requires INSTANCE_SECRET and INTERNAL_SERVICE_TOKEN in .env.self-hosted.
Caddy fails to start — Rebuild the service: docker compose -f docker-compose.self-hosted.yml --env-file .env.self-hosted up -d --build caddy.
Convex backend exits on startup — Regenerate secrets with pnpm self-hosted:secrets -- --write .env.self-hosted and recreate convex-backend. Do not use placeholder INSTANCE_SECRET values.
Port conflict — Change *_PORT in .env.self-hosted and update SELF_HOSTED_*_VERIFY_URL if you use custom verification URLs.
/voice/context returns 401 — Run pnpm self-hosted:convex:env so INTERNAL_SERVICE_TOKEN matches between Convex and the voice gateway.
pnpm self-hosted:verify fails on Convex backend origins — Set CONVEX_CLOUD_ORIGIN to match CONVEX_URL and CONVEX_SITE_ORIGIN to match CONVEX_SITE_URL, then rerun pnpm self-hosted:convex:env.
voice convex connectivity fails in pnpm self-hosted:verify — Confirm convex-backend is healthy, pnpm self-hosted:convex:env has run, and recreate the voice gateway: docker compose -f docker-compose.self-hosted.yml --env-file .env.self-hosted up -d --build voice-gateway.
In-browser web calls fail with origin errors — Set WEB_CALL_ALLOWED_ORIGINS to the exact web URL you open in the browser, including http://127.0.0.1:8080 for localhost smoke.
/voice/context returns 404 — The HTTP route is reachable; create tenant data or assign a matching business phone number.
Convex functions missing provider configuration — Run pnpm self-hosted:convex:env after updating .env.self-hosted.