Prerequisites

  • Docker and Docker Compose installed
  • A server with a public IP address
  • A domain name pointed to your server
  • Ports 80, 443 (TCP) and 40000-40100 (UDP) open in your firewall

1. Clone and Configure

git clone https://github.com/sellserv/voice-server.git
cd voice-server/deploy/self-hosted
cp .env.example .env
# Edit .env — set JWT_SECRET and DOMAIN
nano .env

Required:

  • JWT_SECRET — Generate with openssl rand -hex 32
  • DOMAIN — Your domain (e.g. chat.example.com) for Caddy/Let's Encrypt
  • MEDIASOUP_ANNOUNCED_IP — Your server's public IP address

Recommended:

  • ADMIN_USERS — Comma-separated admin user IDs (set in .env)
  • RESEND_API_KEY + EMAIL_FROM — For email MFA and verification

2. Start the Server

docker compose up -d

Your instance is now running at https://your-domain (Caddy handles HTTPS automatically).

3. Admin Setup

  1. Register an account on your instance
  2. Verify your email (if email is configured)
  3. Add your user ID (UUID) to ADMIN_USERS in .env
  4. Restart: docker compose up -d

Updating

docker compose pull
docker compose up -d

Data and uploads persist across updates via Docker volumes. Database migrations run automatically on startup.

All Environment Variables

VariableRequiredDescription
JWT_SECRETYesRun openssl rand -hex 32
DOMAINYesYour domain (for Caddy / Let's Encrypt)
MEDIASOUP_ANNOUNCED_IPYesServer's public IP for WebRTC voice
CORS_ORIGINSYesYour domain (e.g. https://chat.example.com)
RESEND_API_KEYYesFor email verification, MFA, and password resets (resend.com)
EMAIL_FROMYesSender address (e.g. noreply@example.com)
ADMIN_USERSRecommendedComma-separated admin user IDs (set in .env)

Optional Features

VariableDescription
GIPHY_API_KEYEnables GIF picker
TURNSTILE_SITE_KEY + TURNSTILE_SECRET_KEYCloudflare CAPTCHA on registration
FIREBASE_SERVICE_ACCOUNTFirebase Admin SDK JSON for mobile push notifications

Firewall Ports

PortProtocolPurpose
22TCPSSH
80 / 443TCPCaddy — HTTP / HTTPS
40000-40100UDPmediasoup RTP (voice/video)

Backups

Back up the Docker volumes to save your entire instance:

# Find volume paths
docker volume inspect sellserv-data sellserv-uploads

# Or copy from the container
docker cp sellserv:/app/data ./backup-data
docker cp sellserv:/app/uploads ./backup-uploads