1. Different ROS_DOMAIN_ID
Both machines must use the same domain:
$ echo $ROS_DOMAIN_IDUnset means 0. Set the same value on both: export ROS_DOMAIN_ID=7. For Styri, pass it to the container: -e ROS_DOMAIN_ID=7.
2. Discovery limited to localhost
On Jazzy, check ROS_AUTOMATIC_DISCOVERY_RANGE: it must not be LOCALHOST or OFF (unset means SUBNET, which is what you want). Also check ROS_LOCALHOST_ONLY is not 1: it’s deprecated on Jazzy but still honored, and it overrides the discovery range. On Humble, check ROS_LOCALHOST_ONLY is not 1.
3. Multicast blocked
ROS 2 finds other machines with multicast. Many Wi-Fi networks and managed switches block it. Test it, starting the receiver first:
On machine A:
$ ros2 multicast receiveOn machine B:
$ ros2 multicast sendIf nothing arrives, multicast is blocked; swap the machines to test the other direction. Use a wired network, ask IT to allow multicast (IGMP) on the robot’s network, or skip multicast with unicast peers: ROS_STATIC_PEERS on Jazzy, or on either distro your DDS implementation’s own setting (a Fast DDS discovery server, or Cyclone DDS peers).
4. Firewall
DDS uses UDP, starting at port 7400 for domain 0; each domain ID adds 250 (domain 7 starts at 9150). Allow UDP from that port up between the machines, plus multicast.
5. Docker networking
A container on Docker’s default network can’t see DDS discovery. Use --network host on Linux. On macOS and Windows, Docker runs in a VM and can’t see a robot on another machine; run it on the robot or a Linux box instead.
Check it with Styri
$ docker run --rm --network host -v "$PWD/styri.yaml:/etc/styri/styri.yaml:ro" ghcr.io/styri-dev/styri-core:v0.3.1 styri checkstyri check prints the domain it’s on and lists every mapped topic as PASS or FAIL, so you can confirm discovery works from the machine running the panel. “Not on the network” for every topic points to one of the five causes above; for one topic, it names the closest match it can see.