# 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