tools/selfhost/ brings up the entire cloud relay on a
single host: a self-hosted Convex backend plus its dashboard, the Mission
Control web GCS, the MQTT broker and bridge, and the video relay. Start here
when you want everything in one place. If you only need the telemetry relay or
only the video relay, run the per-tool compose files under
tools/mqtt-bridge/deploy/ and tools/video-relay/deploy/ instead.
The stack lives in
github.com/altnautica/ADOSMissionControl
under GPL-3.0, in tools/selfhost/.
You do not need any of this to fly on the LAN. Served over plain HTTP, Mission
Control connects straight to the agent and pairs over the local network with no
backend. The cloud relay is the opt-in path for reaching a drone across
networks. See the
self-hosting overview for the local-first
path.
Services and ports
The compose project directory isselfhost, so Compose names the containers
selfhost-<service>-1.
| Service | Image or build | Port | Used by |
|---|---|---|---|
convex-backend (client API) | ghcr.io/get-convex/convex-backend | 3210 | the GCS browser bundle (NEXT_PUBLIC_CONVEX_URL), convex deploy |
convex-backend (site / HTTP actions) | same container | 3211 | the agent heartbeat and the MQTT bridge (CONVEX_URL) |
convex-dashboard | ghcr.io/get-convex/convex-dashboard | 6791 | admin dashboard UI |
mission-control | built from the repo Dockerfile | 4000 | the web GCS |
mosquitto | eclipse-mosquitto:2 | 1883 (TCP), 9001 (WebSocket) | MQTT |
mqtt-bridge | built from tools/mqtt-bridge | internal | forwards MQTT telemetry to Convex |
video-relay | built from tools/video-relay | 3001 | RTSP-to-WebSocket video |
mission-control service builds from the repo root Dockerfile, and the
mqtt-bridge and video-relay services build from their own folders. The
backend and dashboard pull prebuilt images.
Critical port wiring
A self-hosted Convex backend exposes two origins, and they are not interchangeable.| Origin | Role | Who talks to it |
|---|---|---|
:3210 | client API | the GCS browser bundle (NEXT_PUBLIC_CONVEX_URL); convex deploy pushes functions here |
:3211 | site / HTTP actions | the drone agent heartbeat and the MQTT bridge (CONVEX_URL resolves to ${CONVEX_URL}/agent/status); the agent’s server.self_hosted.url and pairing.convex_url |
:3210 for the browser and deploy, :3211 for the agent and bridge.
Environment
Copy the example file and edit it before bringing anything up.A short instance name for the self-hosted backend (for example
ados-selfhost).A long random secret for the backend. Generate one with
openssl rand -hex 32.The client-API origin the backend advertises (
:3210). On a single host
reachable at <host>, point this at the published port. Use your LAN IP or
hostname for <host>.The site / HTTP-actions origin the backend advertises (
:3211).The Convex client-API origin baked into the GCS build (
:3210). This is a
build argument, so it is inlined at image build time, not read at runtime.The Convex site origin (
:3211) the MQTT bridge and agent POST to. The bridge
sends to ${CONVEX_URL}/agent/status.The MQTT username. Defaults to
ados.The MQTT password. You also write this into a password file in the setup steps
below.
The RTSP source pattern the video relay pulls from.
{deviceId} is substituted
at runtime with the drone’s device id. Defaults to
rtsp://host.docker.internal:8554/{deviceId}.Setup
The Convex backend needs functions and auth keys pushed in out-of-band before the rest of the stack is useful, so the order matters: start the backend, deploy functions, set the keys, create the MQTT password, then bring up everything.Push Convex functions and schema
Deploy the GCS functions from a machine with the repo checked out. Point
the CLI at your
:3210 origin and the admin key from the previous step:Generate and set the auth keys
A fresh backend has no signing keys. The helper script exports
JWT_PRIVATE_KEY and JWKS into your shell; set them plus SITE_URL (the
GCS origin the browser loads, the mission-control container on port
4000) on the backend:Create the MQTT password file
Mosquitto reads a password file mounted into the container. Start the
broker on its own, then write the password for the Use the same password you set as
ados user:MQTT_PASSWORD in .env.Optional relay layers
The MQTT bridge and the video relay are additive. The GCS reads their public URLs from Convex environment variables at runtime, so set them on the backend (point them at however you expose Mosquitto’s WebSocket port and the relay):MQTT bridge
Mosquitto plus the bridge that forwards live telemetry to Convex.
Video relay
RTSP-to-WebSocket video so a browser can play the drone’s stream.
Point the agent at the stack
To put a drone on this backend instead of LAN-only local mode, set itsserver.mode to self_hosted and point both the self-hosted URL and the
pairing URL at the Convex site origin (:3211):
server and pairing blocks, and the
agent self-hosting page for the install
command.
Verify
Open the GCS athttp://<host>:4000. Over HTTPS it enters cloud mode; pair a
drone and the node detail shows a Cloud badge and starts receiving telemetry. A
few quick checks:
curl http://<host>:3210/versionreturns a version, so the backend is reachable on the client-API origin.- Sign-in works in the GCS, which confirms the auth keys are set on the right deployment.
- A paired drone appears in the fleet, which confirms its heartbeat reached the
:3211site origin.
:3210 versus :3211 wiring above.
A single Mission Control image that runs the GCS and pushes Convex functions to
the backend on boot would remove the out-of-band
convex deploy step. That is
a planned change, not built yet; functions are deployed manually for now.Where to go next
Mission Control and Convex
Build and run the GCS image and the Convex backend on their own.
Drone agent
Install the agent and point it at your backend.
Troubleshooting and ports
The port table and the common failure modes.