skyhub_core repository and turns an NVIDIA Jetson plus an ArduPilot/Pixhawk flight controller and SIYI/RealSense cameras into an internet-connected, AI-capable drone or rover. Everything is packaged as a set of docker-compose services, all running with network_mode: host and restart: always, glued together by three interconnect fabrics: MAVLink (routed by a single mavp2p hub), ROS 2 DDS, and a Redis message bus.
From the cloud’s perspective, the drone is reached at exactly one control endpoint — rosbridge on port 9090 — which the Gateway Service connects to over a per-drone WireGuard VPN. This page is the map of what runs behind that port. Deep-dives on each area live in the sibling pages linked throughout.
Despite the name,
skycore_cli.py at the repo root is not the OS entrypoint or service orchestrator. It is a host-run ArduPilot maintenance CLI (nav-source toggle, params, log/terrain download, camera SD). The real “boot” is docker-compose profiles plus supervisord inside the core container. See the SkyCore CLI Reference.The six services
The production topology is defined indocker-compose.yml. Each service is gated by a Compose profile, and COMPOSE_PROFILES selects which ones start on boot.
| Service (compose name) | Profile | What it does | Detail page |
|---|---|---|---|
core | core | ROS 2 service: MAVROS + rosbridge (:9090) + drone_node, which loads the video_stream, aruco_landing, and battery modules. The drone’s cloud-facing brain. | Services · Module System |
ws_proxy | gamepad | FastAPI WebSocket (:5001 /gamepad) for manual control, safe GUIDED velocity, gimbal/zoom, and arm/disarm asset upload (HLS video, photos, logs) to S3. | Safe Control · Video |
mavproxy | mavproxy | The mavp2p MAVLink router. Bridges the FC serial link to a fixed UDP port map for every other service. | MAVLink Routing |
rtk-ntrip | rtk | NTRIP client that injects RTCM corrections into the FC for RTK-fixed GPS and sends GGA back to the caster. | RTK NTRIP |
camera-proxy | camera | Re-serves the SIYI RTSP camera as a local endpoint (:8554 /stream HQ passthrough + /fast_stream HW re-encoded for YOLO). | Video Streaming |
isaac-slam | slam | GPU service: Isaac ROS Visual SLAM + nvblox + docking + TTS audio. Bridges VIO pose into the FC EKF for GPS-denied nav. | SLAM Navigation |
System topology
The three interconnect fabrics
Every service talks to the others over one of exactly three buses. Understanding which one carries what is the fastest way to trace a bug.MAVLink via mavp2p
A single router owns the FC serial port and fans MAVLink out over UDP. The port map is load-bearing.
ROS 2 DDS
Shared
ROS_DOMAIN_ID over host networking. Carries /mavros/* topics and the video-room string topics.Redis pub/sub
Per-drone
{ip}:channel command and status bus, keyed by the drone’s WireGuard IP.1. MAVLink — the mavp2p hub
mavproxy runs the mavp2p router. It is the single owner of the flight-controller serial link and fans MAVLink out over a fixed UDP port map that every other service must agree on (docker-compose.yml:139):
docker-compose.yml (mavproxy command)
| UDP port | Consumer |
|---|---|
14550 | core MAVROS (FCUURL=udp://127.0.0.1:14550@) |
14777 | gamepad MAVLink handler (MAVLINK=udpout:127.0.0.1:14777) |
14560 | rtk-ntrip RTCM injection |
14561 | core ArUco-landing direct velocity control |
14900 | External GCS (e.g. QGroundControl over the VPN) |
2. ROS 2 DDS
Thecore container’s MAVROS publishes the /mavros/* topic tree, drone_node consumes /mavros/state, and isaac-slam subscribes to the same topics — all over DDS on a shared ROS_DOMAIN_ID (default 1) across the host network. drone_node (docker/core/main.py:67) also carries the Janus video-room config as std_msgs/String topics (video_room_details, video_room_state, video_room_source), which the Gateway publishes over rosbridge.
3. Redis message bus
The manual-control and status plane is Redis pub/sub. Channels are namespaced by the drone’s WireGuardwg0 IP — {ip}:gamepad_input, {ip}:output, {ip}:chat, {ip}:aruco_tracking, and more — so one Redis can serve many drones without cross-talk. Two channels are deliberately global (unprefixed): video_stream_state and video_stream_status_request. The channel catalog and dual command path (Redis relay vs. direct WebSocket) are documented in the Message Bus page.
If the
wg0 interface can’t be resolved (e.g. local dev with no VPN), you must set IP_OVERRIDE or the gamepad service raises RuntimeError at boot. The legacy misspelling IP_OVRIDE is still honored for backwards compatibility.How the cloud attaches
The Gateway never speaks MAVLink or Redis directly to a drone in the command path — it attaches at rosbridge:9090 and pub/subs ROS topics and calls ROS services. From there:
- Commands (arm, takeoff, set-mode, missions) become rosbridge
call_service/publishframes → MAVROS → mavp2p → the FC. See the Drone Control Service. - Telemetry flows the reverse way: MAVROS topics → rosbridge → Gateway → Socket.IO rooms → the Dashboard. Note
set_stream_rates.shmust raise the FC stream rate to 10 Hz first, or sensor topics stay empty. - Manual gamepad control takes a separate low-latency path: Dashboard → WS Proxy → Redis
{ip}:gamepad_input→ the drone’sws_proxy/:5001service, not through rosbridge. See Vehicle Commands. - Video is fully out-of-band: the on-drone GStreamer pipeline whipsinks H264 to the WHIP server, which registers a Janus VideoRoom publisher the Dashboard subscribes to over WebRTC. See Video Streaming.
Service boot and dependencies
docker-compose up reads COMPOSE_PROFILES and starts the selected services. All are network_mode: host and restart: always. Startup order is delay-based, not health-gated (except in the SITL overlay): each service sleeps <SERVICE>_STARTDELAY seconds before starting (DRONE_STARTDELAY=5, RTK_STARTDELAY=10, etc.), reinforced by depends_on (core and rtk-ntrip depend on mavproxy; isaac-slam depends on mavproxy, core, and ws_proxy).
Inside the core container there is a second init layer: supervisord (docker/core/supervisord.conf) is PID-1-like and launches five processes in priority order. drone_node starts last (priority 999) so MAVROS, rosbridge, and stream rates are ready before it subscribes.
Gotchas a future editor must preserve
network_mode: hosteverywhere. There is no Docker port isolation — ports9090,5001,8554, and the145xxMAVLink ports are on the host. Moving to bridged networking would break both DDS discovery and the127.0.0.1MAVLink loopback assumptions.mavp2pis a single point of failure and un-configurable at runtime (endpoints are CLI args). The port map above is load-bearing.- Two independent module systems share the names
ModuleLoader/ModuleBase: the ROS 2 env-registry loader incorevs. the explicitly-registered loader ingamepad. They are different code — do not merge them blindly. See Module System. - Movement is GUIDED-mode
SET_POSITION_TARGET_LOCAL_NED, notRC_CHANNELS_OVERRIDE. Zero velocity means hold, and control only activates when user-enabled and the vehicle is in GUIDED, with a dead-man command-timeout watchdog. All three guards are safety-critical — see Safe Control. - The video overlay is intentionally not burned into the stream (the “canvas approach”): ArUco tracking goes to Redis
{ip}:aruco_trackingfor a frontend canvas overlay, to save Jetson GPU and preserve video quality.setup_video_overlay()is a deliberate no-op. - The
isaac-slamdirectory is misspelledissac-slamon disk, while the Compose service and image useisaac. The bind mount hardcodes an absolute host path into/home.
Local development
There is a hardware-free path:docker-compose.local.yml overlays an ArduPilot SITL container and a local Redis (10.223.0.2) on a bridge network that mimics the VPN (10.223.0.0/16), disables the hardware services (mavproxy, camera-proxy, rtk-ntrip, isaac-slam), switches to the TEST camera and a software encoder, and injects IP_OVERRIDE so Redis channels resolve without wg0.
sitl.sh selects the vehicle type and home location.
Where to go next
Microservices & Container Profiles
Per-service reference: env vars, network mode, and startup ordering.
Redis Message Bus & WebSocket Interfaces
The full channel catalog and the dual command path.
MAVLink Routing (mavp2p)
The port map and flight-control link in detail.
Guided Velocity Control & Safety Model
The safe manual-flight control that replaced RC override.
Isaac Visual SLAM & Pose Bridge
GPS-denied navigation via VIO pose injection.
SkyCore CLI Reference
The host-run ArduPilot maintenance tool.

