Problem: ROS 2 Nodes Can't Find Each Other in Docker
You launched ROS 2 nodes in separate Docker containers and ros2 topic list shows nothing. Nodes can't discover each other even though they're on the same host.
You'll learn:
- Why DDS discovery fails in Docker by default
- How to configure Docker networking for ROS 2
- When to use host mode vs bridge with multicast
Time: 15 min | Level: Intermediate
Why This Happens
ROS 2 uses DDS (Data Distribution Service) for discovery via multicast UDP packets on 239.255.0.1:7400. Docker's default bridge network doesn't forward multicast traffic between containers, so nodes never see each other's announcements.
Common symptoms:
ros2 topic listis empty or only shows local topicsros2 node listdoesn't show nodes from other containers- Works fine when running natively, breaks in Docker
- No errors, just silent failure
Solution
Step 1: Verify the Issue
# Terminal 1: Start first container
docker run -it --rm --name ros2_pub \
ros:humble-ros-base \
ros2 topic pub /test std_msgs/String "data: hello"
# Terminal 2: Try to see it from another container
docker run -it --rm --name ros2_sub \
ros:humble-ros-base \
ros2 topic list
Expected: You'll only see /rosout and /parameter_events, not /test
Step 2: Choose Your Network Strategy
Option A: Host Network (Simplest)
Use when all containers run on the same physical machine:
# Terminal 1
docker run -it --rm --network host --name ros2_pub \
ros:humble-ros-base \
ros2 topic pub /test std_msgs/String "data: hello"
# Terminal 2
docker run -it --rm --network host --name ros2_sub \
ros:humble-ros-base \
ros2 topic echo /test
Why this works: Host mode bypasses Docker networking entirely. Containers share the host's network stack, so multicast works like native ROS 2.
Trade-off: No network isolation. Port conflicts possible.
Option B: Bridge with Multicast (Production)
Use when you need network isolation or multi-host setup:
# Create a custom network with multicast support
docker network create \
--driver bridge \
--subnet 172.18.0.0/16 \
--gateway 172.18.0.1 \
ros2_network
# Run containers with specific IPs
docker run -it --rm \
--network ros2_network \
--ip 172.18.0.10 \
-e ROS_DOMAIN_ID=42 \
-e FASTRTPS_DEFAULT_PROFILES_FILE=/tmp/fastdds.xml \
--name ros2_pub \
ros:humble-ros-base bash
Inside the container, create FastDDS config:
<!-- /tmp/fastdds.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<dds>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<transport_descriptors>
<transport_descriptor>
<transport_id>udp_transport</transport_id>
<type>UDPv4</type>
<sendBufferSize>1048576</sendBufferSize>
<receiveBufferSize>1048576</receiveBufferSize>
</transport_descriptor>
</transport_descriptors>
<participant profile_name="default_participant" is_default_profile="true">
<rtps>
<userTransports>
<transport_id>udp_transport</transport_id>
</userTransports>
<useBuiltinTransports>false</useBuiltinTransports>
<!-- Force discovery through specific interfaces -->
<builtin>
<metatrafficUnicastLocatorList>
<locator>
<udpv4>
<address>172.18.0.10</address>
</udpv4>
</locator>
</metatrafficUnicastLocatorList>
<initialPeersList>
<locator>
<udpv4>
<address>172.18.0.11</address>
</udpv4>
</locator>
</initialPeersList>
</builtin>
</rtps>
</participant>
</profiles>
</dds>
Why this works: Unicast discovery with explicit peer lists bypasses multicast issues. Each node knows where to look for peers.
Option C: Docker Compose (Recommended for Dev)
Best for local development with multiple services:
# docker-compose.yml
version: '3.8'
services:
talker:
image: ros:humble-ros-base
command: ros2 run demo_nodes_cpp talker
network_mode: host
environment:
- ROS_DOMAIN_ID=0
ipc: host
listener:
image: ros:humble-ros-base
command: ros2 run demo_nodes_cpp listener
network_mode: host
environment:
- ROS_DOMAIN_ID=0
ipc: host
Why ipc: host: Enables shared memory transport for faster communication when containers are on same host.
Step 3: Use Domain IDs to Isolate
# Prevent interference from other ROS 2 systems
export ROS_DOMAIN_ID=42 # 0-101 for ROS 2, 215-232 for custom
# In Docker run command
docker run -e ROS_DOMAIN_ID=42 ...
Best practice: Use unique domain IDs per project to avoid cross-talk.
Step 4: Verify Discovery
# Check if nodes see each other
ros2 node list
# Verify topic connections
ros2 node info /talker
# Monitor discovery traffic (debug)
ros2 run demo_nodes_cpp talker --ros-args --log-level debug
You should see:
- All nodes from both containers in
ros2 node list - Publisher/subscriber counts match expectations
- Messages flowing with
ros2 topic echo
If it fails:
- "No nodes found": Check
ROS_DOMAIN_IDmatches in all containers - Firewall blocking: Run
sudo ufw allow 7400:7500/udpon host - Still broken: Use
tcpdumpto verify multicast packets:tcpdump -i any -n udp port 7400
Verification
Run the full test:
# Start compose stack
docker-compose up
# In another terminal
docker exec -it <container_id> ros2 topic list
You should see: Topics from both talker and listener, messages flowing without errors.
What You Learned
- Docker bridge networks drop multicast by default
- Host mode works for single-machine setups (simplest)
- FastDDS unicast discovery solves multi-host scenarios
- Always set
ROS_DOMAIN_IDto avoid interference
Limitations:
- Host mode sacrifices network isolation
- Unicast discovery requires knowing peer IPs upfront
- Multi-host needs proper network planning
When NOT to use this:
- High-security environments (host mode exposes everything)
- Kubernetes (use ROS 2 DDS config for K8s networking instead)
Bonus: Production Dockerfile
FROM ros:humble-ros-base
# Install your packages
RUN apt-get update && apt-get install -y \
ros-humble-demo-nodes-cpp \
&& rm -rf /var/lib/apt/lists/*
# Copy FastDDS config
COPY fastdds.xml /etc/fastdds.xml
ENV FASTRTPS_DEFAULT_PROFILES_FILE=/etc/fastdds.xml
# Set domain ID at runtime
ENV ROS_DOMAIN_ID=42
# Use cyclonedds for simpler multicast (alternative to FastDDS)
RUN apt-get update && apt-get install -y ros-humble-rmw-cyclonedds-cpp
ENV RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]
CycloneDDS tip: Often handles Docker networking better than FastDDS out of the box. Try switching RMW implementations if discovery issues persist.
Tested on ROS 2 Humble, Docker 25.x, Ubuntu 22.04 & macOS with Docker Desktop