- Add systemd unit examples and README for local install (Linux) - Extend deployment README with Run at boot (local install) and merge upstream - Add Re-syncing auth tokens subsection to gateway provisioning troubleshooting - install.sh: add --install-service to install systemd user units (Linux) - DOCUMENTATION.md: session notes Made-with: Cursor
Deployment
This section covers deploying Mission Control in self-hosted environments.
Goal A simple, reproducible deploy that preserves the Postgres volume and supports safe upgrades.
Deployment mode: single host (Docker Compose)
Prerequisites
- Docker + Docker Compose v2 (
docker compose) - A host where the browser can reach the backend URL you configure (see
NEXT_PUBLIC_API_URLbelow)
1) Configure environment
From repo root:
cp .env.example .env
Edit .env:
AUTH_MODE=local(default)- Set
LOCAL_AUTH_TOKENto a non-placeholder value (≥ 50 chars) - Ensure
NEXT_PUBLIC_API_URLis reachable from the browser (not a Docker-internal hostname)
Key variables (from .env.example / compose.yml):
- Frontend:
FRONTEND_PORT(default3000) - Backend:
BACKEND_PORT(default8000) - Postgres:
POSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_PORT - Backend:
DB_AUTO_MIGRATE(defaulttruein compose)CORS_ORIGINS(defaulthttp://localhost:3000)
- Security headers (see configuration reference):
SECURITY_HEADER_X_CONTENT_TYPE_OPTIONS(defaultnosniff)SECURITY_HEADER_X_FRAME_OPTIONS(defaultDENY)SECURITY_HEADER_REFERRER_POLICY(defaultstrict-origin-when-cross-origin)
2) Start the stack
docker compose -f compose.yml --env-file .env up -d --build
Open:
- Frontend:
http://localhost:${FRONTEND_PORT:-3000} - Backend health:
http://localhost:${BACKEND_PORT:-8000}/healthz
To have containers restart on failure and after host reboot, add restart: unless-stopped to the db, redis, backend, and frontend services in compose.yml, and ensure Docker is configured to start at boot.
3) Verify
curl -f "http://localhost:${BACKEND_PORT:-8000}/healthz"
If the frontend loads but API calls fail, double-check:
NEXT_PUBLIC_API_URLis set and reachable from the browser- backend CORS includes the frontend origin (
CORS_ORIGINS)
Database persistence
The Compose stack uses a named volume:
postgres_data→/var/lib/postgresql/data
This means:
docker compose ... downpreserves datadocker compose ... down -vis destructive (deletes the DB volume)
Migrations / upgrades
Default behavior in Compose
In compose.yml, the backend container defaults:
DB_AUTO_MIGRATE=true
So on startup the backend will attempt to run Alembic migrations automatically.
Warning
For zero/near-zero downtime, migrations must be backward compatible with the currently running app if you do rolling deploys.
Safer operator pattern (manual migrations)
If you want more control, set DB_AUTO_MIGRATE=false and run migrations explicitly during deploy:
cd backend
uv run alembic upgrade head
Container security
Both the backend and frontend Docker containers run as a non-root user (appuser). This is a security hardening measure.
If you bind-mount host directories into the containers, ensure the mounted paths are readable (and writable, if needed) by the container's non-root user. You can check the UID/GID with:
docker compose exec backend id
Reverse proxy / TLS
Typical setup (outline):
- Put the frontend behind HTTPS (reverse proxy)
- Ensure the frontend can reach the backend over the configured
NEXT_PUBLIC_API_URL
This section is intentionally minimal until we standardize a recommended proxy (Caddy/Nginx/Traefik).
Run at boot (local install)
If you installed Mission Control without Docker (e.g. using install.sh with "local" mode, or inside a VM where Docker is not used), the installer does not configure run-at-boot. You can start the stack after each reboot manually, or configure the OS to start it for you.
Linux (systemd)
Use the example systemd units and instructions in systemd/README.md. In short:
- Copy the unit files from
docs/deployment/systemd/and replaceREPO_ROOT,BACKEND_PORT, andFRONTEND_PORTwith your paths and ports. - Install the units under
~/.config/systemd/user/(user) or/etc/systemd/system/(system). - Enable and start the backend, frontend, and RQ worker services.
The RQ queue worker is required for gateway lifecycle (wake/check-in) and webhook delivery; run it as a separate unit.
macOS (launchd)
Use LaunchAgents so the backend, frontend, and worker run under your user and restart on failure.
- Create a plist for each process under
~/Library/LaunchAgents/, e.g.com.openclaw.mission-control.backend.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.openclaw.mission-control.backend</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/env</string>
<string>uv</string>
<string>run</string>
<string>uvicorn</string>
<string>app.main:app</string>
<string>--host</string>
<string>0.0.0.0</string>
<string>--port</string>
<string>8000</string>
</array>
<key>WorkingDirectory</key>
<string>REPO_ROOT/backend</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/opt/homebrew/bin:REPO_ROOT/backend/.venv/bin</string>
</dict>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Replace REPO_ROOT with the actual repo path. Ensure uv is on PATH (e.g. add ~/.local/bin to the PATH in the plist). Load with:
launchctl load ~/Library/LaunchAgents/com.openclaw.mission-control.backend.plist
- Add similar plists for the frontend (
npm run start -- --hostname 0.0.0.0 --port 3000inREPO_ROOT/frontend) and for the RQ worker (uv run python ../scripts/rq workerwithWorkingDirectory=REPO_ROOT/backendandProgramArgumentspointing atuv,run,python,../scripts/rq,worker).