Skip to main content
Choose this path when you already control a server (cheapest option; ideal for co-locating with a self-hosted LiveKit server). The installer script handles Docker, secrets, image pinning, health checking, and HTTPS in a single command:
It prompts for backend (SQLite or Postgres) and whether to set up HTTPS. For non-interactive use:
The script:
  • Installs Docker if not present (with confirmation)
  • Generates and persists the ingest key and Postgres password on first run, reuses them on later runs (no password-regen footgun)
  • Pins the image to the latest release version (never :latest)
  • Health-checks the container before returning
  • Installs Caddy and issues a certificate automatically if ports 80/443 are free and a domain is given; otherwise prints a reverse-proxy snippet for your existing proxy
The ingest key is printed to your terminal and saved to the deploy directory (/opt/voicegateway/voicegw.yaml by default). Use it as the api_key when connecting agents.

Manual setup

Prerequisites: Docker and Compose installed (curl -fsSL https://get.docker.com | sh).
1

Download Compose file and generate secrets

2

Create the ingest key

The ingest key must not start with vk_.
3

Start the stack

Run the above exactly once. Re-running it regenerates the Postgres password but the existing volume keeps the old one, causing authentication failures. If you need to re-run, bring the stack down first and remove the volume: docker compose down -v. The one-line installer avoids this footgun by persisting secrets across runs.
Postgres runs as a service in this Compose (self-hosted). To use a managed database instead, drop the postgres service and set VOICEGW_DB_URL=postgresql+asyncpg://... (for example a Neon URL) on the collector service.

Expose over HTTPS

Fresh box (install Caddy)

Install Caddy via the official apt repository:
Create /etc/caddy/Caddyfile:
Reload Caddy and open the firewall:
For safety, bind the daemon to localhost only by changing the compose port mapping to 127.0.0.1:8080:8080 so only Caddy is internet-facing. Point a DNS A record collector.<your-domain> at the VPS IP.

Reuse an existing reverse proxy

If the box already runs a reverse proxy on 80/443 (for example a self-hosted LiveKit server whose Caddy runs with host networking), it reaches the daemon at localhost:8080 with no extra wiring: the daemon publishes 8080 on the host, and a host-networked proxy shares the host’s network namespace. Add a vhost or TLS-SNI route for collector.<your-domain> pointing to localhost:8080. Back up the proxy config first and reload it gracefully. For LiveKit’s layer-4 Caddy (structured caddy.yaml), add a TLS-SNI route and include the hostname in apps.tls.certificates.automate:
Reload with caddy reload --config /etc/caddy.yaml --adapter yaml (validates before applying; LiveKit stays up if the config is invalid).

Security

Only /v1/ingest, /health, and (if you point LiveKit webhooks at VoiceGateway) /v1/livekit/webhook need to be public. Put the dashboard and /v1/api-keys behind basic auth at the proxy level, or reach them via an SSH tunnel from your local machine./v1/livekit/webhook carries no bearer token because LiveKit cannot send one: it authenticates every request by verifying the LiveKit webhook signature against your API key and secret, and rejects the request before reading the body. It fails closed, so a deployment with no LiveKit credentials configured rejects every webhook rather than accepting unsigned writes.

Verify

Follow the steps at Verify, using https://collector.<your-domain> as the daemon URL and the ingest key printed during setup.

Connect your agent

See Connect your agent. Use https://collector.<your-domain> as collector_url and the ingest key as api_key.