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.

By default anyone who can reach your instance can sign up and create their own team. Lock it down in .env:

VariableEffect when set to true
DISABLE_SIGNUPNew accounts need a team invitation link. The very first account of a fresh instance can still be created.
DISABLE_PASSWORD_LOGINEmail/password sign-in, sign-up and password reset are rejected. Only the configured OAuth providers are left.

Notes:

  • With DISABLE_SIGNUP, create your admin account first, then invite teammates from the dashboard. Existing users keep signing in as before.
  • DISABLE_PASSWORD_LOGIN requires Google or GitHub login to be configured (GOOGLE_* or GITHUB_*), otherwise the backend refuses to start. Slack alone does not count, the web app has no Slack login button.
  • OAuth providers without a key and secret are not offered on the login page.
  • Billing is off unless STRIPE_SECRET_KEY is set. Without it every team has full access and the web app hides the subscription page.

Restart the backend after changing these: docker compose up -d backend.

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).

Web app: telemetry (PostHog analytics) is disabled by default in selfhosted builds. No data is sent to external services.

Desktop app: when connecting to a selfhosted backend, disable telemetry from the desktop app Settings.

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.