Cloud Infrastructure
The cloud layer is optional. Every core ADOS function works without internet. But when you want remote monitoring, fleet management, or observer access from across the internet, three relay layers provide increasing levels of real-time capability.Three layers
On the agent, all three layers are the work of one native Rust service,ados-cloud: it POSTs status to Convex, publishes telemetry to MQTT, and handles WebRTC signaling over MQTT. The agent reaches Convex directly, so the cloud-side MQTT-to-Convex bridge is an optional helper that keeps Convex fresh from the higher-rate MQTT stream, not a required hop.
Layer 1: Convex HTTP (baseline)
The simplest relay. The agent’s native Rust cloud service (ados-cloud) POSTs a JSON status payload to the Convex backend every 5 seconds. Mission Control uses Convex’s reactive queries to display the data in real time.
This layer requires only outbound HTTPS from the agent. No port forwarding, no MQTT, no special setup. If the agent can reach the internet, status shows up in the GCS.
Convex tables
Two custom tables power the cloud relay:cmd_droneStatus: Stores the latest status for each device. Upserted on every POST. Reactive query in the browser delivers changes immediately.cmd_droneCommands: Command queue. Mission Control enqueues commands (arm, disarm, mode change). The agent polls this table and ACKs each command after execution.
Layer 2: MQTT (real-time telemetry)
For higher-frequency data, the agent publishes to MQTT topics via a Mosquitto broker behind a Cloudflare Tunnel. Mission Control subscribes from the browser using MQTT.js over WebSocket.
The MQTT-to-Convex bridge runs alongside the broker. It subscribes to all
ados/+/status and ados/+/telemetry topics, debounces 3 seconds per device, and POSTs the latest data to Convex. This keeps the Convex tables fresh for clients that use reactive queries instead of direct MQTT.
Why MQTT, not just Convex polling
Convex reactive queries are great for UI updates but the minimum granularity is tied to the 5-second HTTP POST cycle. MQTT gives true 2 Hz telemetry with ~200 ms latency. For a remote operator watching a live mission, the difference between 5-second updates and 500 ms updates is significant. MQTT also handles unreliable connections better. QoS 1 ensures delivery even if the TCP connection momentarily drops.Layer 3: WebRTC video (peer-to-peer)
Live video does not go through a cloud media server. Instead, the browser and agent establish a direct WebRTC peer-to-peer connection. The MQTT broker acts as the signaling relay. The signaling flow:- Browser publishes an SDP offer to
ados/{deviceId}/webrtc/offer - The agent’s
ados-cloudservice receives the offer via MQTT - Agent creates a WebRTC answer using the local MediaMTX WHEP endpoint
- Agent publishes the SDP answer to
ados/{deviceId}/webrtc/answer - Browser receives the answer, ICE candidates are exchanged
- WebRTC media stream flows directly between browser and agent (peer-to-peer)
P2P WebRTC requires both sides to be able to reach each other after STUN-negotiated NAT traversal. About 85-90% of networks support this. The remaining 10-15% (symmetric NAT on some cellular carriers) need a TURN relay, which is not yet deployed. Those users see a clear error in the transport switcher.
Infrastructure layout
All cloud services are co-located on a single Linux server:
Everything routes through Cloudflare Tunnels. No inbound ports are open on the server. The Tunnel client (
cloudflared) maintains outbound connections to Cloudflare’s edge.
Self-hosting
You can run the entire cloud stack on your own hardware using Docker Compose.Convex backend
MQTT broker + bridge
The MQTT broker and bridge are inADOSMissionControl/tools/mqtt-bridge/:
.env to point the bridge at your Convex deployment.
Agent configuration
Point your agent at your own cloud. The agent reads its cloud posture fromserver.mode and a matching block. For a self-hosted backend, set mode: self_hosted and fill in server.self_hosted:
server keys (defaults shown):
There is no
cloud: top-level section. Keys like convex_url or mqtt_ws_url do not exist; use the schema above.
Cloudflare Tunnel setup
If you want to expose your self-hosted services without port forwarding:1
Install cloudflared
2
Create a tunnel
3
Configure hostname routing
Create
~/.cloudflared/config.yml:4
Start the tunnel
Bandwidth and cost
The entire cloud relay stack can run at zero monthly cost for small deployments.
What is next
- Video Stack for the full video pipeline details
- System Overview for the three-tier architecture
- Project Structure for where cloud code lives