1.Reverse-proxy the main app domain
With an SSL certificate on port 443, proxy location / to the main app (http://127.0.0.1:80 in Docker mode, http://127.0.0.1:5180 bare-metal). Three critical lines: proxy_http_version 1.1, proxy_set_header Upgrade $http_upgrade, proxy_set_header Connection "upgrade" — miss any one and the /socket.io WebSocket upgrade fails with 400. The frontend container/template already forwards /api to the backend, so this outer layer needs no separate API routing.
2.Update two env values and restart api
Add the new domain to CORS_ALLOWED_ORIGINS in api/.env (comma-separated) and set PUBLIC_API_BASE_URL to the main app's public root address (such as https://app.your-domain.com, without /api). Restart api for the change to take effect.
3.Protect the ops endpoints
/admin/* shares the same backend as the main app. If you call the ops endpoints from the public internet, add an IP allowlist or Basic Auth on top so ADMIN_SECRET can't be brute-forced; also make sure the proxy passes the custom header through explicitly: proxy_set_header X-Admin-Secret $http_x_admin_secret; (some proxies filter non-standard headers by default).
Troubleshooting: the frontend reports Failed to fetch
Open the browser devtools Network tab and inspect the request: if an /api request came back as text/html, nginx didn't forward /api to the backend and the SPA fallback caught it; if it's a CORS rejection, CORS_ALLOWED_ORIGINS doesn't include the domain you're visiting from.
Troubleshooting: WebSocket 400
The /socket.io/ location must explicitly carry the Upgrade / Connection headers with proxy_http_version 1.1 — missing any one of them yields 400. With multiple proxy layers, every layer needs them.
Related questions
Can I self-host Enclave? Is it hard?
Yes, and it's about as hard as running an ordinary web service. If you've used docker compose, three steps get you your own instance: clone the repo, copy .env, and docker compose up. The API, frontend, database, and vector index all run on your own machine.