Skip to content

Self-Hosting

Run your own Hopp server. Includes PostgreSQL, Redis, LiveKit (WebRTC), and Caddy (auto-TLS).

  • A server with Docker and Docker Compose installed
  • A public hostname for your server. Either:
    • A real domain with A records for <DOMAIN> and livekit.<DOMAIN> (or wildcard *.<DOMAIN>) pointing to your server’s IP.
    • Or sslip.io for quick testing — use <your-ip>.sslip.io as DOMAIN (e.g. 51.15.42.10.sslip.io). Resolves automatically, no DNS setup. Subdomains work (livekit.<your-ip>.sslip.io). Real domain recommended for production.
  • Open firewall ports (see Firewall below)
Terminal window
git clone --depth 1 https://github.com/gethopp/hopp.git
cd hopp/selfhost
Terminal window
cp .env.example .env

Edit .env and set at minimum:

DOMAIN=hopp.example.com
SESSION_SECRET=<openssl rand -base64 32>
LIVEKIT_API_KEY=<openssl rand -base64 32>
LIVEKIT_API_SECRET=<openssl rand -base64 64>
POSTGRES_PASSWORD=<openssl rand -base64 24>

Generate secrets with the openssl commands shown.

Terminal window
docker compose up -d
Terminal window
curl https://hopp.example.com/api/health
# → OK

Open https://hopp.example.com in a browser and sign up. The first registered account becomes the team admin owner.

Many cloud providers (Scaleway DEV, Hetzner Cloud, basic DigitalOcean droplets) do not apply a firewall by default — these ports will already be reachable. Skip this section unless your provider has a security group, network ACL, or you’ve enabled ufw/firewalld on the host.

AWS, GCP, Azure, and Scaleway PRO/PROD instances typically need explicit security-group rules.

Open these ports if your provider applies a firewall:

PortProtocolPurpose
80TCPHTTP (TLS challenge)
443TCPHTTPS
7881TCPLiveKit TCP fallback
50000-60000UDPWebRTC media

Caddy handles TLS termination on ports 80/443. LiveKit media bypasses Caddy and goes directly to the server.

The web app served by your backend works without a rebuild — it derives the API URL from window.location at runtime.

For the desktop app, you can set a custom backend URL at runtime via Settings > Custom Backend URL (see Settings docs).

Copy compose.override.example.yml to compose.override.yml and adjust port mappings.

If you already run nginx/traefik/caddy, skip the bundled Caddy:

  1. Remove the caddy service from compose.yml (or use an override)
  2. Point your proxy to backend:1926 (Docker DNS) and 127.0.0.1:7880 (LiveKit on host networking)
  3. Set USE_TLS=false (already the default)
  4. Set LIVEKIT_SERVER_URL=wss://livekit.yourdomain.com in .env

Local development (running selfhost stack on your machine)

Section titled “Local development (running selfhost stack on your machine)”

To run the self-hosted stack against localhost instead of a public domain:

The backend serves TLS directly when USE_TLS=true. Generate certs from the backend/ folder:

Terminal window
cd ../backend
task create-certs # uses mkcert; installs the local CA on first run

This produces backend/certs/localhost.pem and backend/certs/localhost-key.pem, which the override mounts into the backend container.

A ready-made compose.override.yml is committed for local use. It:

  • exposes backend:1926 directly on the host (skipping Caddy)
  • enables USE_TLS=true and mounts ../backend/certs
  • swaps LiveKit to livekit.dev.yaml (narrow UDP range 50000-50100, avoids macOS port-conflict storms)

Set .env:

DOMAIN=localhost
LIVEKIT_SERVER_URL=ws://localhost:7880
USE_TLS=true

Then:

Terminal window
docker compose up -d db cache livekit backend
# (skip caddy — not needed for localhost)

Backend reachable at https://localhost:1926.