mission-control/docs/deployment.md

8.2 KiB

Deployment Guide

Prerequisites

  • Node.js >= 20 (LTS recommended)
  • pnpm (installed via corepack: corepack enable && corepack prepare pnpm@latest --activate)

Ubuntu / Debian

better-sqlite3 requires native compilation tools:

sudo apt-get update
sudo apt-get install -y python3 make g++

macOS

Xcode command line tools are required:

xcode-select --install

Quick Start (Development)

cp .env.example .env.local
pnpm install
pnpm dev

Open http://localhost:3000. Login with AUTH_USER / AUTH_PASS from your .env.local.

Production (Direct)

pnpm install --frozen-lockfile
pnpm build
pnpm start

The pnpm start script binds to 0.0.0.0:3005. Override with:

PORT=3000 pnpm start

Important: The production build bundles platform-specific native binaries. You must run pnpm install and pnpm build on the same OS and architecture as the target server. A build created on macOS will not work on Linux.

Production (Standalone)

Use this for bare-metal deployments that run Next's standalone server directly. This path is preferred over ad hoc node .next/standalone/server.js because it syncs .next/static and public/ into the standalone bundle before launch.

pnpm install --frozen-lockfile
pnpm build
pnpm start:standalone

For a full in-place update on the target host:

BRANCH=fix/refactor PORT=3000 pnpm deploy:standalone

What deploy:standalone does:

  • fetches and fast-forwards the requested branch
  • reinstalls dependencies with the lockfile
  • rebuilds from a clean .next/
  • stops the old process bound to the target port
  • starts the standalone server through scripts/start-standalone.sh
  • verifies that the rendered login page references a CSS asset and that the CSS is served as text/css

Production (Docker)

docker build -t mission-control .
docker run -p 3000:3000 \
  -v mission-control-data:/app/.data \
  -e AUTH_USER=admin \
  -e AUTH_PASS=your-secure-password \
  -e API_KEY=your-api-key \
  mission-control

The Docker image:

  • Builds from node:20-slim with multi-stage build
  • Compiles better-sqlite3 natively inside the container (Linux x64)
  • Uses Next.js standalone output for minimal image size
  • Runs as non-root user nextjs
  • Exposes port 3000 (override with -e PORT=8080)

Persistent Data

SQLite database is stored in /app/.data/ inside the container. Mount a volume to persist data across restarts:

docker run -v /path/to/data:/app/.data ...

Environment Variables

See .env.example for the full list. Key variables:

Variable Required Default Description
AUTH_USER Yes admin Admin username (seeded on first run)
AUTH_PASS Yes - Admin password
AUTH_PASS_B64 No - Base64-encoded admin password (overrides AUTH_PASS if set)
API_KEY Yes - API key for headless access
PORT No 3005 (direct) / 3000 (Docker) Server port
OPENCLAW_HOME No - Path to OpenClaw installation
MC_ALLOWED_HOSTS No localhost,127.0.0.1 Allowed hosts in production

Kubernetes Sidecar Deployment

When running Mission Control alongside a gateway as containers in the same pod (sidecar pattern), agents are not discovered via the filesystem. Instead, use the gateway's agent registration API.

Architecture

┌──────────────── Pod ────────────────┐
│  ┌─────────┐     ┌───────────────┐  │
│  │   MC    │◄───►│   Gateway     │  │
│  │ :3000   │     │   :18789      │  │
│  └─────────┘     └───────────────┘  │
│       ▲                  ▲          │
│       │ localhost         │          │
│       └──────────────────┘          │
└─────────────────────────────────────┘

Required Configuration

Environment variables for the MC container:

AUTH_USER=admin
AUTH_PASS=<secure-password>
API_KEY=<your-api-key>
OPENCLAW_GATEWAY_HOST=127.0.0.1
NEXT_PUBLIC_GATEWAY_PORT=18789

Agent Registration

The gateway must register its agents with MC on startup. Include the agents array in the gateway registration request:

curl -X POST http://localhost:3000/api/gateways \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "sidecar-gateway",
    "host": "127.0.0.1",
    "port": 18789,
    "is_primary": true,
    "agents": [
      { "name": "developer-1", "role": "developer" },
      { "name": "researcher-1", "role": "researcher" }
    ]
  }'

To update the agent list on reconnect, use PUT /api/gateways with the same agents field.

Alternatively, each agent can register itself via the direct connection endpoint:

curl -X POST http://localhost:3000/api/connect \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "tool_name": "openclaw-gateway",
    "agent_name": "developer-1",
    "agent_role": "developer"
  }'

Health Checks

Agents must send heartbeats to stay visible:

curl http://localhost:3000/api/agents/<agent-id>/heartbeat \
  -H "Authorization: Bearer <API_KEY>"

Without heartbeats, agents will be marked offline after 10 minutes (configurable via general.agent_timeout_minutes setting).

Troubleshooting

"Module not found: better-sqlite3"

Native compilation failed. On Ubuntu/Debian:

sudo apt-get install -y python3 make g++
rm -rf node_modules
pnpm install

AUTH_PASS with "#" is not working

In dotenv files, # starts a comment unless the value is quoted.

Use one of these:

  • AUTH_PASS="my#password"
  • AUTH_PASS_B64=$(echo -n 'my#password' | base64)

"pnpm-lock.yaml not found" during Docker build

If your deployment context omits pnpm-lock.yaml, Docker build now falls back to pnpm install --no-frozen-lockfile.

For reproducible builds, include pnpm-lock.yaml in the build context.

"Invalid ELF header" or "Mach-O" errors

The native binary was compiled on a different platform. Rebuild:

rm -rf node_modules .next
pnpm install
pnpm build

Database locked errors

Ensure only one instance is running against the same .data/ directory. SQLite uses WAL mode but does not support multiple writers.

"Gateway error: origin not allowed"

Your gateway is rejecting the Mission Control browser origin. Add the Control UI origin to your gateway config allowlist, for example:

{
  "gateway": {
    "controlUi": {
      "allowedOrigins": ["http://YOUR_HOST:3000"]
    }
  }
}

Then restart the gateway and reconnect from Mission Control.

"Gateway error: device identity required"

Device identity signing uses WebCrypto and requires a secure browser context. Open Mission Control over HTTPS (or localhost), then reconnect.

"Gateway shows offline on VPS deployment"

Browser WebSocket connections to non-standard ports (like 18789/18790) are often blocked by VPS firewall/provider rules.

Quick option:

NEXT_PUBLIC_GATEWAY_OPTIONAL=true

This runs Mission Control in standalone mode (core features available, live gateway streams unavailable).

Production option: reverse-proxy gateway WebSocket over 443.

nginx example:

location /gateway-ws {
  proxy_pass http://127.0.0.1:18789;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  proxy_set_header Host $host;
  proxy_read_timeout 86400;
}

Then point UI to:

NEXT_PUBLIC_GATEWAY_URL=wss://your-domain.com/gateway-ws

Next Steps

Once deployed, set up your agents and orchestration:

  • Quickstart — Register your first agent and complete a task in 5 minutes
  • Agent Setup — SOUL personalities, heartbeats, config sync, agent sources
  • Orchestration Patterns — Auto-dispatch, quality review, multi-agent workflows
  • CLI Reference — Full CLI command list for headless/scripted usage