Deployment & networking
Loma’s Docker Compose stack includes a bundled nginx reverse proxy as the single external
entrypoint. The backend (:3000) and dashboard (:3001) are bound to 127.0.0.1 only, so you
expose just one port and the backend never touches the public internet.
client ──▶ nginx ──┬─ /api/auth/* , /api/signup ─▶ dashboard (sign-in)
├─ /api/* ─▶ backend (identity injected by the proxy)
├─ /webhooks/* , /webhook ─▶ backend (third-party webhooks)
└─ everything else ─▶ dashboard (UI)
For every /api/* call the proxy makes an internal auth_request to the dashboard to resolve the
signed-in user and inject a trusted identity header before forwarding to the backend — so the
backend always knows the caller’s role, and a client can’t spoof it.
HTTP on an IP (default)
Out of the box the proxy serves HTTP on :80 (server_name _) — works locally and on a bare
IP, no domain needed. Open inbound 80 in your firewall/security group (leave 3000/3001
closed), set:
.env:PUBLIC_BASE_URL=http://<your-ip>dashboard/.env:AUTH_URL=http://<your-ip>
then docker compose up -d and open http://<your-ip>.
HTTPS on :443 (a few steps)
HTTPS is opt-in via the committed docker-compose.tls.yml overlay, which renders an nginx TLS
config for your domain from ${LOMA_DOMAIN} — no hardcoded domain, and the repo default stays
HTTP. Prereqs: a DNS A record for your domain → the server’s (ideally static/Elastic) IP, and
inbound 443 open.
-
Get a certificate while the default HTTP stack is up (it already serves the ACME challenge on
:80):docker compose run --rm certbot certonly --webroot -w /var/www/certbot \ -d your-domain.com --email you@example.com --agree-tos --no-eff-email -
Enable HTTPS — in
.env:LOMA_DOMAIN=your-domain.com COMPOSE_FILE=docker-compose.yml:docker-compose.tls.yml PUBLIC_BASE_URL=https://your-domain.comand in
dashboard/.env:AUTH_URL=https://your-domain.com. -
docker compose up -d— nginx now serves:443and redirects:80 → :443. Renew with a cron that runscertbot renewand reloads nginx.
LOMA_DOMAIN/COMPOSE_FILE live in your untracked .env and certs in an untracked volume, so
HTTPS survives a git pull redeploy. Full runbook: deploy/nginx/README.md in the repo.
Behind a CDN (e.g. Cloudflare)
If your domain is proxied through a CDN, TLS is terminated at the edge. Secure the CDN→origin hop
with an origin certificate (e.g. Cloudflare’s, with Full (Strict)), restrict the origin’s
:80/:443 to the CDN’s IP ranges (or enable authenticated origin pulls), and keep
X-Forwarded-Proto: https flowing (the proxy already sets it).
Configuration behind the proxy
- Set
PUBLIC_BASE_URL(backend.env) andAUTH_URL(dashboard/.env) to the public origin (http://<ip>orhttps://your-domain.com). - Keep
BACKEND_URL=http://loma-backend:3000— the internal address the dashboard proxies to. - Provider webhooks should point at
https://your-domain.com/webhooks/...(Slack uses Socket Mode and is unaffected). - A domain + HTTPS is also what unlocks Google sign-in — see Authentication.
Backups
Back up all three stores:
- MongoDB — conversations, skills, flows, users, integrations, usage.
LOMA_SKILL_ASSET_DIR— non-text skill assets.- The Claude-users volume — connected Claude account credentials for the pool.
- Your TLS certs (
deploy/nginx/certbot/conf) if you’re not regenerating them.