ROS 2 is built for distributed systems, but its default networking model works best on a local network. Connecting a robot to a remote operator, cloud service, or another robot on a different network often means teams have to build and maintain their own transport layer around the ROS graph.
Today, we're releasing LiveKit Portal for ROS 2. It's a standalone ROS 2 node that connects a ROS graph to a LiveKit room. It gives ROS and non-ROS participants a common way to exchange topics, service calls, and video over WebRTC, using LiveKit's realtime network.

LiveKit Portal for ROS 2 lets you build remote robotics systems without exposing DDS across networks, changing the nodes that already run your robot, or building and maintaining your own transport layer.
Connect your ROS graphs to any LiveKit participant#Link to this section
A ROS Portal node discovers configured topics in the local ROS graph and publishes them as LiveKit tracks. Once published, another ROS Portal node can subscribe and republish those tracks into its own graph. And, for instances where robotics systems are communicating with non-ROS clients, web applications, cloud services, and other LiveKit clients can consume the same data without running ROS.
ROS Portal supports four main data paths:
- ROS topics. Forward topics in either direction through LiveKit DataTracks. ROS Portal discovers message types at runtime and advertises a schema with each track.
- ROS services. Route configured service calls to a specific LiveKit participant through LiveKit RPC.
- ROS images. Publish
sensor_msgs/msg/Imageorsensor_msgs/msg/CompressedImagetopics as LiveKit VideoTracks with configurable codec and bitrate options. - Video Sources. Bypass ROS and publish video directly as LiveKit VideoTracks.
ROS Portal stores each topic's message schema with its own DataTrack, so every subscriber knows how to read the data:
- For ROS-to-ROS connections, messages stay in ROS's native CDR format, with no per-message conversion. Before forwarding a topic, ROS Portal checks that both graphs use the same message definition.
- For ROS-to-non-ROS connections, ROS Portal can encode a topic as JSON and publish a JSON Schema generated from the ROS message type, so a browser app or cloud service can read it without ROS tooling.

What you can build with ROS Portal#Link to this section
With Portal, DDS remains inside each robot's local ROS network. LiveKit handles the wide-area connection between participants, including encryption, congestion control, and selective subscriptions. Each remote participant subscribes only to the tracks it needs.
This model supports several common use cases:
- Teleoperation. Stream video and sensor data to an operator interface, then send control topics back to the robot.
- Remote inference. Run compute-heavy ROS components or native LiveKit clients on remote machines.
- Fleet coordination. Exchange selected topics and service calls between robots without joining their DDS networks.
- Monitoring. Send diagnostics and state to browser dashboards or cloud services as schema-described JSON.
Control what crosses the network#Link to this section
ROS Portal uses a YAML configuration file to define which topics and services can cross the LiveKit connection. Topic rules support regular expressions, direction controls, rate limits, schema encoding, latched delivery, and video options.
The following configuration publishes test topics in both directions, limits an IMU stream, receives remote velocity commands, and publishes a stereo camera directly on a LiveKit VideoTrack:
1ros_portal:2 version: '0.0.1'3 4 topics:5 - topic: '/test/.*'6 direction: 'bidirectional'7 8 - topic: '/imu/data_raw'9 direction: 'out'10 max_rate_hz: 1011 encoding: 'jsonschema'12 13 - topic: '/remote/cmd_vel'14 direction: 'in'15 16 services:17 - service: '/navigate_to_pose'18 direction: 'out'19 participant: 'robot-a'20 msg_type: 'nav2_msgs/srv/NavigateToPose'21 22 video_sources:23 - track_name: 'stereo_camera'24 source:25 type: 'gstreamer'26 pipeline: >-27 v4l2src device=/dev/video0 ! videoconvert !28 x264enc name=lk_encoder tune=zerolatency ! h264parse !29 appsink name=lk_appsink30 codec: 'h264'31 resolution:32 width: 256033 height: 72034 rate_control:35 element: 'lk_encoder'36 property: 'bitrate'37 unit: 'kbps'38 publish_options:39 max_bitrate_bps: 400000040 max_framerate: 30
Only forward the topics and directions that your application needs. This keeps unnecessary traffic off constrained robot links and limits the remote surface of the ROS graph.
Demo: offload SLAM to a remote GPU#Link to this section
This demo brings three use cases together: teleoperation, remote inference, and monitoring. A rover streams its stereo camera through LiveKit, and an operator who doesn't run ROS drives it around a scene. A ROS participant with a desktop GPU performs SLAM and dense scene reconstruction. Finally, the robot's pose and a growing 3D map are sent to the LiveKit room and visualized by another ROS participant.
The rover is a Waveshare Cobra Flex with a Jetson Orin Nano and a relatively cheap USB stereo camera. The Jetson runs the drive stack, but it doesn't have the GPU headroom for learned depth estimation. A desktop with an NVIDIA RTX 5090 does that work instead. Nor does the edge device inherently enable remote viewing or control. The machines don't share a local network or a DDS domain.
The demo has four LiveKit participants:
- Robot. The cobra_flex_ros Docker Compose stack runs the rover drivers and ROS Portal. ROS Portal publishes the stereo camera as a LiveKit VideoTrack and republishes incoming velocity commands as ROS
cmd_veltopics. - Operator. A browser app built with the LiveKit JS SDK, teleop, shows the camera feed and sends joystick and keyboard commands. It doesn't run ROS.
- SLAM host. The remote_ros_slam Docker Compose stack runs ROS Portal, RTAB-Map stereo SLAM, and ScaRF-SLAM scene reconstruction on the desktop.
- Visualizer. A Mac runs ROS Portal and Foxglove Bridge. ROS Portal republishes the pose and map topics into the Mac's local ROS graph, and Foxglove renders them.
How the SLAM host builds the 3D map#Link to this section
Each camera frame holds the left and right images side by side, so both eyes share one timestamp. ROS Portal on the SLAM host receives the VideoTrack and republishes it as a ROS image topic. The SLAM nodes read that topic like any local camera.
The rover has no IMU, so RTAB-Map computes poses from stereo odometry and loop closure. ScaRF-SLAM takes RTAB-Map's keyframes, estimates dense depth on the GPU, and adds to the map in batches while the rover drives. The map trails the rover by a few seconds.
The SLAM host's ROS Portal configuration takes the camera in and sends only the results out:
1ros_portal:2 version: '0.0.1'3 4 topics:5 - topic: '/stereo_camera'6 direction: 'in'7 8 # Transforms: map -> odom -> stereo_link from RTAB-Map, and the camera9 # frames from the launch file.10 - topic: '/tf'11 direction: 'out'12 13 - topic: '/tf_static'14 direction: 'out'15 latched: true16 17 # Loop-corrected trajectory.18 - topic: '/rtabmap/mapPath'19 direction: 'out'20 21 - topic: '/rtabmap/map'22 direction: 'out'23 max_rate_hz: 0.524 25 - topic: '/scarf_slam/submaps/.*'26 direction: 'out'27 28 - topic: '/scarf_slam/slam_poses'29 direction: 'out'
The SLAM host doesn't publish video. Participants that want the camera view subscribe to the robot's stereo_camera track directly through LiveKit, which leaves the SLAM host's uplink for map data.
The full ScaRF-SLAM map grows to several megabytes per message, so it stays in the SLAM host's local graph. The host instead sends voxelized, compressed chunks of at most 32 KB, each on its own topic.
The /scarf_slam/submaps/.* rule forwards new chunk topics as they appear. Foxglove keeps the latest message on each topic, so the chunks together render the whole map.
The demo in three views. Top left: the teleoperator's view in the browser, from a participant that doesn't run ROS. Bottom left: a ROS participant's Foxglove view of the realtime video. Right: Foxglove's view of the scene reconstruction.
None of the SLAM nodes know about LiveKit. They read and write ordinary ROS topics, and the ROS Portal configuration decides what crosses the network. You can use the same pattern for other heavy components, such as perception models, planners, or policy inference.
Try ROS Portal locally with Docker#Link to this section
ROS Portal is available as Docker images and Debian packages for supported ROS 2 distributions and architectures. You can connect it to LiveKit Cloud or a self-hosted LiveKit server.
For a local setup, start a LiveKit server with participant data blobs enabled:
1livekit-server --dev --enable_participant_data_blob
Then create a token for the ROS Portal participant:
1export LIVEKIT_URL=ws://127.0.0.1:78802export LIVEKIT_TOKEN="$(lk token create \3 --join \4 --room robo_room \5 --identity ros-portal \6 --allow-update-metadata \7 --valid-for 24h \8 --token-only \9 --yes \10 --dev)"
Create /tmp/all_topics.yaml for a quick local test:
1echo 'ros_portal:2 version: "0.0.1"3 topics:4 - topic: ".*"5 direction: "bidirectional"' > /tmp/all_topics.yaml
Run the container for your ROS distribution:
1docker run --rm \2 --network host \3 --env LIVEKIT_URL \4 --env LIVEKIT_TOKEN \5 --volume /tmp/all_topics.yaml:/config/ros_portal.yaml:ro \6 livekit/ros-portal:<ros_distro> \7 ros2 launch ros_portal ros_portal.launch.py \8 config_path:=/config/ros_portal.yaml
The current release supports ROS 2 Humble, Jazzy, Kilted, and Lyrical.
Next steps#Link to this section
The repository includes installation guides, a complete configuration reference, Docker instructions, and a Turtlesim tutorial for connecting two ROS graphs.
View ROS Portal on GitHub
Follow the Turtlesim tutorial