From 67cba01996b4a92e152c7678fe5e2e483b1c152a Mon Sep 17 00:00:00 2001 From: "Arjun (OpenClaw)" Date: Sat, 7 Feb 2026 17:42:06 +0000 Subject: [PATCH 1/2] docs: add production deployment guide Adds docs/production/README.md with single-VPS and multi-VPS deployment patterns, reverse proxy + TLS baseline, secrets strategy, and ops checklist. --- docs/production/README.md | 206 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 docs/production/README.md diff --git a/docs/production/README.md b/docs/production/README.md new file mode 100644 index 00000000..be5fbdb2 --- /dev/null +++ b/docs/production/README.md @@ -0,0 +1,206 @@ +# Production deployment (production-ish) + +This document describes **production-ish** deployment patterns for **OpenClaw Mission Control**. + +Mission Control is a web app (frontend) + API (backend) + Postgres + Redis. The simplest reliable +baseline is Docker Compose plus a reverse proxy with TLS. + +> This repo currently ships a developer-friendly `compose.yml`. For real production, you should: +> - put Postgres/Redis on managed services or dedicated hosts when possible +> - terminate TLS at a reverse proxy +> - set up backups + upgrades +> - restrict network exposure (firewall) + +## Recommended baseline + +- Docker Engine + Docker Compose v2 +- Reverse proxy: **Caddy** (simplest) or **nginx** +- TLS via Let’s Encrypt +- Persistent storage for Postgres +- Centralized logs (or at least log rotation) + +## Single VPS (all-in-one) + +### Architecture + +On one VM: + +- Caddy/nginx (ports 80/443) → routes traffic to: + - frontend container (internal port 3000) + - backend container (internal port 8000) +- Postgres container (internal 5432) +- Redis container (internal 6379) + +### Ports / firewall + +Expose to the internet: + +- `80/tcp` and `443/tcp` only + +Do **not** expose: + +- Postgres 5432 +- Redis 6379 +- backend 8000 +- frontend 3000 + +All of those should be reachable only on the docker network / localhost. + +### Environment & secrets + +Recommended approach: + +- Keep a host-level directory (e.g. `/opt/mission-control/`) +- Store runtime env in **non-committed** files: + - `/opt/mission-control/.env` (compose-level vars) + - optionally `/opt/mission-control/backend.env` and `/opt/mission-control/frontend.env` + +Secrets guidelines: + +- Never commit Clerk secret key. +- Prefer passing secrets as environment variables from the host (or use Docker secrets if you later + migrate to Swarm/K8s). +- Rotate secrets if they ever hit logs. + +### Compose in production + +Clone the repo on the VPS, then: + +```bash +cd /opt +sudo git clone https://github.com/abhi1693/openclaw-mission-control.git mission-control +cd mission-control + +cp .env.example .env +# edit .env with real values (domains, Clerk keys, etc.) + +docker compose -f compose.yml --env-file .env up -d --build +``` + +### Reverse proxy (Caddy example) + +Example `Caddyfile` (adjust domain): + +```caddyfile +mission-control.example.com { + encode gzip + + # Frontend + reverse_proxy /* localhost:3000 + + # (Optional) If you want to route API separately, use a path prefix: + # reverse_proxy /api/* localhost:8000 +} +``` + +Notes: +- If the frontend calls the backend directly, ensure `NEXT_PUBLIC_API_URL` points to the public API + URL, not `localhost`. +- If you route the backend under a path prefix, ensure backend routing supports it (or put it on a + subdomain like `api.mission-control.example.com`). + +### Keep services running (systemd) + +Docker restart policies are often enough, but for predictable boot/shutdown and easy ops, use +systemd. + +Create `/etc/systemd/system/mission-control.service`: + +```ini +[Unit] +Description=Mission Control (docker compose) +Requires=docker.service +After=docker.service + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=/opt/mission-control + +ExecStart=/usr/bin/docker compose -f compose.yml --env-file .env up -d +ExecStop=/usr/bin/docker compose -f compose.yml --env-file .env down + +TimeoutStartSec=0 + +[Install] +WantedBy=multi-user.target +``` + +Enable: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now mission-control +sudo systemctl status mission-control +``` + +### Backups + +Minimum viable: + +- Nightly `pg_dump` to off-host storage +- Or filesystem-level backup of the Postgres volume (requires consistent snapshots) + +Example dump: + +```bash +docker exec -t openclaw-mission-control-db-1 pg_dump -U postgres mission_control > /opt/backups/mission_control.sql +``` + +## Multi-VPS (split services) + +The main reason to split is reliability and blast-radius reduction. + +### Option A: 2 hosts + +- Host 1: reverse proxy + frontend + backend +- Host 2: Postgres + Redis (or managed) + +### Option B: 3 hosts + +- Host 1: reverse proxy + frontend +- Host 2: backend +- Host 3: Postgres + Redis (or managed) + +### Networking / security groups + +Minimum rules: + +- Public internet → reverse proxy host: `80/443` +- Reverse proxy host → backend host: `8000` (or whatever you publish internally) +- Backend host → DB host: `5432` +- Backend host → Redis host: `6379` + +Everything else: deny. + +### Configuration considerations + +- `DATABASE_URL` must point to the DB host (not `localhost`). +- `REDIS_URL` must point to the Redis host. +- `CORS_ORIGINS` must include the public frontend URL. +- `NEXT_PUBLIC_API_URL` should be the public API base URL. + +### Database migrations + +The backend currently runs Alembic migrations on startup (see logs). In multi-host setups: + +- Decide if migrations should run automatically (one backend instance) or via a manual deploy step. +- Avoid multiple concurrent backend deploys racing on migrations. + +## Operational checklist + +- [ ] TLS is enabled, HTTP redirects to HTTPS +- [ ] Only 80/443 exposed publicly +- [ ] Postgres/Redis not publicly accessible +- [ ] Backups tested (restore drill) +- [ ] Log retention/rotation configured +- [ ] Regular upgrade process (pull latest, rebuild, restart) + +## Troubleshooting (production) + +- `docker compose ps` and `docker compose logs --tail=200` are your first stops. +- If the UI loads but API calls fail, check: + - `NEXT_PUBLIC_API_URL` + - backend CORS settings (`CORS_ORIGINS`) + - firewall rules between proxy ↔ backend + From 27aaf47390c7d9d3def6c27c233e5c56045c4041 Mon Sep 17 00:00:00 2001 From: "Sana (OpenClaw)" Date: Sun, 8 Feb 2026 16:33:38 +0000 Subject: [PATCH 2/2] docs: clarify Clerk required + NEXT_PUBLIC_API_URL example --- docs/production/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/production/README.md b/docs/production/README.md index be5fbdb2..07dd894b 100644 --- a/docs/production/README.md +++ b/docs/production/README.md @@ -13,6 +13,10 @@ baseline is Docker Compose plus a reverse proxy with TLS. ## Recommended baseline +If you’re looking for the **dev-friendly self-host** path (single machine, Docker Compose defaults), start with the repo root README: +- [Quick start (self-host with Docker Compose)](../../README.md#quick-start-self-host-with-docker-compose) + + - Docker Engine + Docker Compose v2 - Reverse proxy: **Caddy** (simplest) or **nginx** - TLS via Let’s Encrypt @@ -57,6 +61,7 @@ Recommended approach: Secrets guidelines: +- **Clerk auth is required for now**: you must configure Clerk keys/JWKS for the app to work. - Never commit Clerk secret key. - Prefer passing secrets as environment variables from the host (or use Docker secrets if you later migrate to Swarm/K8s). @@ -94,8 +99,9 @@ mission-control.example.com { ``` Notes: -- If the frontend calls the backend directly, ensure `NEXT_PUBLIC_API_URL` points to the public API +- If the frontend calls the backend directly, ensure `NEXT_PUBLIC_API_URL` points to the **public, browser-reachable** API URL, not `localhost`. + - Example: `NEXT_PUBLIC_API_URL=https://api.mission-control.example.com` - If you route the backend under a path prefix, ensure backend routing supports it (or put it on a subdomain like `api.mission-control.example.com`).