ORB ships an optional Reflex-based web dashboard, opt-in via:
pip install 'orb-py[ui]'Three runtime modes are available, controlled by the ui.mode configuration key:
| Mode | Config value | When to use |
|---|---|---|
| Embedded | embedded (default) |
Single-process production deployments |
| Split | split |
Reverse-proxy / CDN setups, multi-replica API |
| Dev | dev |
Local UI development only (requires Node/Bun) |
A single Reflex backend process serves everything on one port:
- The SPA pages (
/,/machines,/requests, etc.) - WebSocket state sync (
/_event) — required for Reflex interactivity - File upload (
/_upload) - ORB REST API (
/orb/api/v1/*) — mounted via Reflex'sapi_transformer - Health endpoint (
/orb/health) - Metrics (
/orb/metrics)
Requirements: The [ui] extra only. No Node or Bun needed at runtime.
How it works: orb server start spawns reflex run --env prod --backend-only
in the orb/ui package directory with ORB_MODE=embedded. The Reflex app
(orb.ui.app) has api_transformer configured to mount ORB's FastAPI at /orb,
so all API routes are served on the same port alongside the SPA and WebSocket layer.
The pre-built frontend bundle (compiled by make ui-build and shipped in the wheel)
is served directly by the Reflex backend — no Node/Bun process at runtime.
Command:
orb server start
# or, to keep the process in the foreground:
orb server start --foregroundVerify:
# Should return HTML (SPA shell)
curl http://localhost:8000/
# Should return {"status":"ok"}
curl http://localhost:8000/orb/health
# WebSocket — browser connects automatically via the SPA; ws://localhost:8000/_eventNo more "Cannot connect to server: ws://localhost:8001/_event" errors — everything is on a single port.
Security defaults (changed from earlier releases)
The server now binds to
127.0.0.1(loopback) by default instead of0.0.0.0. CORSoriginsdefaults to["http://localhost:8000"]andtrusted_hostsdefaults to["localhost", "127.0.0.1"].To expose the server on a network interface you must set all three explicitly in your config file:
{ "server": { "host": "0.0.0.0", "cors": { "origins": ["https://your-domain.example.com"] }, "trusted_hosts": ["your-domain.example.com"] } }Leaving
hostas0.0.0.0in the CLI help text is intentional — it remains a valid example of how to broaden network exposure.
Two processes are started and managed together by orb server start:
- Process A — uvicorn with ORB's FastAPI on
server_config.port(default 8000). API-only; no Reflex, no SPA. - Process B — Reflex production backend on
ui_config.backend_port(default 8001). Serves the SPA and WebSocket state.ORB_MODE=remote— the API lives in Process A, not mounted here.
Both processes are managed together: SIGINT/SIGTERM are forwarded to both groups and
orb server stop tears down both cleanly.
Use this mode when you need:
- A CDN edge in front of static assets
- Multiple API replicas behind a load balancer
- Independent scaling of the API and UI tiers
Config: Set ui.mode = "split" in your configuration file.
Command:
orb server startorb server stop stops both processes.
nginx reverse proxy sample:
# Reflex WebSocket and SPA — proxy to Process B
location /_event {
proxy_pass http://127.0.0.1:8001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
location / {
proxy_pass http://127.0.0.1:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# ORB API — proxy to Process A
location /orb/ {
proxy_pass http://127.0.0.1:8000/orb/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}Running Process B externally (advanced):
If you want to manage the Reflex backend yourself (outside orb server start):
# Use --api-only on the ORB side to start only the API (Process A):
orb server start --api-only
# Start Reflex separately:
cd $(python -c "import orb.ui, pathlib; print(pathlib.Path(orb.ui.__file__).parent)")
ORB_MODE=remote ORB_UI_BACKEND_PORT=8001 reflex run --env prod --backend-only --backend-port 8001orb server start spawns reflex run (dev mode) as a child process. The Reflex dev
server provides hot reload so UI changes are reflected immediately without restarting
the backend.
Requirements: Node and Bun must be installed on the machine. This mode is not intended for production — the Reflex dev server carries a heavier runtime footprint and is not hardened for public traffic.
Config: Set ui.mode = "dev" in your configuration file.
Ports (from UIConfig):
| Setting | Default | Purpose |
|---|---|---|
frontend_port |
3000 |
Bun/Vite frontend dev server (browser) |
backend_port |
8001 |
Reflex backend + ORB API (api_transformer) |
Command:
# With mode=dev in config:
orb server startDirectly with Reflex (useful for rapid iteration outside of orb server):
cd src/orb/ui # directory containing rxconfig.py
ORB_MODE=dev reflex run --loglevel debugORB_MODE # Deployment mode seen by orb.ui.app:
# "embedded" — api_transformer active (default)
# "remote" — no api_transformer (split Process B)
# "dev" — same as embedded but dev server
ORB_UI_BACKEND_PORT # Reflex backend port.
# embedded: set to server_config.port (e.g. 8000)
# split/dev: set to ui_config.backend_port (e.g. 8001)
# Default: 8001
ORB_UI_FRONTEND_PORT # Dev mode only — Bun frontend dev server port (default 3000)
ORB_API_URL # Full URL of the ORB REST API called by UI state actions
# e.g. http://localhost:8000/orb/api/v1 (embedded)
# e.g. http://localhost:8000/orb/api/v1 (split, points to Process A)
make ui-build
# Outputs to src/orb/ui/_static/
# Requires Node/Bun (Reflex uses Bun to compile the frontend)This step runs automatically in CI before the wheel is packaged, so PyPI releases
always include the pre-compiled bundle. If you are working from a bare source
checkout you must run make ui-build before embedded or split modes will serve
the UI frontend.
Save the following as /etc/systemd/system/orb.service:
[Unit]
Description=ORB server (embedded UI mode)
After=network.target
[Service]
Type=simple
User=orb
ExecStart=/opt/orb/venv/bin/orb server start --foreground
Restart=on-failure
Environment="ORB_CONFIG_DIR=/etc/orb"
[Install]
WantedBy=multi-user.targetEnable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable orb
sudo systemctl start orb
sudo systemctl status orbAdjust ExecStart to match the actual path of the orb binary in your installation.
"Cannot connect to server: ws://localhost:8001/_event"
This error means the browser is connecting to the wrong port. Causes:
- Embedded mode with stale config: The SPA was built with
ORB_UI_BACKEND_PORT=8001baked in. Rebuild the bundle:make ui-buildthen restart the server. - Split mode: The Reflex backend (Process B) is not running on port 8001, or
the reverse proxy is not forwarding
/_eventWebSocket upgrades. - Accessing the port directly instead of via the proxy: In split mode, point your browser at the nginx/proxy port, not at Process A or B directly.
"UI bundle not present at ..."
The installed wheel does not contain _static/. Either install a wheel that was
built with make ui-build (all PyPI releases include this), or run
make ui-build from your source checkout and then restart the server.
Frontend loads but API calls 404
In split mode, check that:
- nginx is forwarding
/orb/*requests to Process A (uvicorn on port 8000). ORB_API_URLin the UI build points to the correct API base URL. The value is baked in at bundle-build time, so if the URL changes you must rebuild withmake ui-build.
Reflex RuntimeError: There should not be an __init__.py file in your app root
Reflex treats the app root directory as a namespace and forbids the presence of
__init__.py in that directory. Ensure src/orb/ui/__init__.py does not exist.
Two production-ready Compose files are provided under deployment/docker/.
Runs a single container that serves the SPA, WebSocket layer, and REST API on port 8000:
docker compose -f deployment/docker/docker-compose.embedded.yml up -dThe container bind-mounts config/config.json (read-only) and uses a named
volume orb-work for runtime state (PID file, loopback token, logs).
When to use: Standard single-host production deployments, self-contained staging environments, or anywhere you want the simplest possible operational footprint. Zero extra services required.
Non-default port: If you need to run on a port other than 8000, you must rebuild the SPA bundle first so the WebSocket URL baked into the JS matches:
ORB_UI_BACKEND_PORT=9000 make ui-build
docker build -t orb-api:custom .
ORB_PORT=9000 docker compose -f deployment/docker/docker-compose.embedded.yml up -dRuns two containers — orb-api (REST API, port 8000) and orb-ui (Reflex
SPA + WebSocket, port 8001) — on a shared Docker network:
docker compose -f deployment/docker/docker-compose.split.yml up -dA reverse proxy (nginx or traefik) must sit in front to unify the two services
on a single public port. An nginx template is included as a commented proxy
service block inside the Compose file — uncomment and adjust the TLS paths to
enable it.
When to use: Deployments that need independent scaling of the API and UI tiers, CDN edge caching of static assets, or multiple API replicas behind a load balancer. Prefer embedded mode for simpler setups.
Key environment variables for split mode:
| Variable | Default | Purpose |
|---|---|---|
ORB_API_PORT |
8000 |
Port for the orb-api container |
ORB_UI_PORT |
8001 |
Port for the orb-ui container |
ORB_BASE_URL |
http://orb-api:8000 |
URL the UI's httpx client uses to reach the API |