This directory contains Docker configurations for running the TidyBot++ ROS 2 platform and NVIDIA Isaac Sim simulator.
docker/
├── tidybot/ # TidyBot platform container
│ ├── Dockerfile # Ubuntu 24.04 + ROS 2 Jazzy + Miniforge + workspace
│ ├── build.sh # Build the TidyBot image
│ ├── run.sh # Run the TidyBot container
│ └── entrypoint.sh # Container entrypoint script
│
├── isaac-sim-ros2/ # Isaac Sim + ROS 2 container
│ ├── Dockerfile # Isaac Sim 5.1.0 + ROS 2 Jazzy
│ ├── build.sh # Build the Isaac Sim image
│ ├── run.sh # Run the Isaac Sim container
│ └── entrypoint.sh # Container entrypoint script
│
├── fastdds.xml # DDS config for local cross-container ROS 2
├── cyclonedds.xml # CycloneDDS config for cross-machine ROS 2
├── diagnose_dds.sh # DDS connectivity diagnostic script
└── README.md # This file
# Build the image (first time only)
./docker/tidybot/build.sh
# Run the container
./docker/tidybot/run.sh restart# Pull the base image (first time only)
docker pull nvcr.io/nvidia/isaac-sim:5.1.0
# Build and run (image is built automatically if not present)
./docker/isaac-sim-ros2/run.sh restart
# Or build manually first
./docker/isaac-sim-ros2/build.sh
./docker/isaac-sim-ros2/run.sh restart| Script | Description |
|---|---|
build.sh |
Build the TidyBot platform Docker image with ROS 2 Jazzy |
run.sh restart |
Stop existing container and start fresh (FastDDS) |
run.sh restart dds=cyclone |
Start fresh with CycloneDDS (for cross-machine VLA) |
run.sh |
Start or attach to existing container |
| Script | Description |
|---|---|
build.sh |
Build the Isaac Sim + ROS 2 Jazzy image |
run.sh restart |
Start the container with Isaac Sim GUI |
run.sh restart shell |
Start container with bash shell only |
run.sh build |
Force rebuild the Docker image |
- NVIDIA GPU with driver version ≥ 535.x
- NVIDIA Container Toolkit installed:
sudo apt-get install nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker
- Base Docker image pulled:
docker pull nvcr.io/nvidia/isaac-sim:5.1.0
| Host Path | Container Path | Description |
|---|---|---|
src/tidybot_description/ |
/tidybot_description |
Robot URDF and meshes |
isaac_sim_workspace/ |
/isaac_workspace |
USD output directory |
~/docker/isaac-sim/ |
Various cache paths | Persistent cache |
Both containers are configured to communicate via ROS 2 DDS using the shared fastdds.xml configuration.
| Setting | Value |
|---|---|
| Network | --network=host |
| ROS_DOMAIN_ID | 0 (configurable via environment) |
| RMW_IMPLEMENTATION | rmw_fastrtps_cpp |
| DDS Config | docker/fastdds.xml |
For communication between the robot and a remote GPU server (e.g., for VLA inference), use CycloneDDS instead of FastDDS. FastDDS fragments published images during cross-machine transport, leading to corrupted data.
Edit docker/cyclonedds.xml on each machine:
Client side (robot / laptop):
<NetworkInterface address="<CLIENT_IP>"/> <!-- This machine's IP -->
<Peer address="<SERVER_IP>"/> <!-- Remote machine's IP -->Server side (GPU server):
<NetworkInterface address="<SERVER_IP>"/> <!-- This machine's IP -->
<Peer address="<CLIENT_IP>"/> <!-- Remote machine's IP -->Both sides must use the same Domain Id and MaxMessageSize.
Pass dds=cyclone to run.sh:
# Client side (robot / laptop)
./docker/tidybot/run.sh restart dds=cyclone
# Server side (GPU server) — outside Docker
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export CYCLONEDDS_URI=$(realpath docker/cyclonedds.xml)
export ROS_DOMAIN_ID=0If CYCLONEDDS_XML is not set, run.sh defaults to docker/cyclonedds.xml.
For full VLA deployment instructions, see src/external/openvla/README.md.
-
Check GPU access:
docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi
-
Check X11 permissions:
xhost +local:
-
On Wayland: Switch to X11 session or set:
export DISPLAY=:0
# Fix permissions for Isaac Sim workspace
chmod -R 777 ~/Documents/tidybot_platform/isaac_sim_workspace/
chmod -R 777 ~/docker/isaac-sim/-
Ensure both containers use the same
ROS_DOMAIN_ID:export ROS_DOMAIN_ID=0 -
Check that
--network=hostis set for both containers -
Verify FastDDS configuration is mounted correctly:
# Inside container cat /fastdds.xml echo $FASTRTPS_DEFAULT_PROFILES_FILE
-
Restart the ROS 2 daemon in both containers:
ros2 daemon stop && ros2 daemon start
- Make sure the simulation is playing (not paused)
- Check that
isaacsim.ros2.bridgeextension is enabled (Window > Extensions) - Verify the ActionGraph is properly configured
- Check the Isaac Sim console for errors
Use the diagnostic script to check both machines:
bash docker/diagnose_dds.sh <PEER_IP>DDS requires bidirectional UDP. Test with:
# On machine A (listen):
python3 -c "
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('0.0.0.0', 7400))
s.settimeout(10)
print('Listening on UDP 7400...')
try:
data, addr = s.recvfrom(1024)
print(f'SUCCESS: Received from {addr}')
except socket.timeout:
print('FAIL: No packet received')
"
# On machine B (send):
python3 -c "
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b'hello', ('<MACHINE_A_IP>', 7400))
print('Sent')
"Test both directions (swap A and B). If one direction fails, the firewall on the receiving machine is blocking inbound UDP.
If ufw is active, it will block inbound UDP by default:
# Check firewall status
sudo ufw status
# Allow UDP from the peer
sudo ufw allow proto udp from <PEER_IP>| Issue | Symptom | Fix |
|---|---|---|
| Domain ID mismatch | No topics discovered | Ensure Domain Id in XML matches ROS_DOMAIN_ID env var on both sides |
| Wrong NetworkInterface | CycloneDDS binds to wrong interface | Set address= to an IP that exists on this machine (ip a to check) |
| Missing peer | No discovery | Each side's <Peer> must point to the other machine's IP |
| MaxMessageSize mismatch | Image corruption | Use 65500B on both sides |
RMW_IMPLEMENTATION not set |
Uses default FastDDS | Must be rmw_cyclonedds_cpp on both sides |
When connecting through a VPN (e.g., Cisco AnyConnect):
- Use the VPN tunnel IP (from
cscotun0) as the NetworkInterface address, not the WiFi IP - The VPN IP is dynamic — update
cyclonedds.xmlon both sides after each VPN reconnect
# Server:
ros2 run demo_nodes_cpp talker
# Client:
ros2 run demo_nodes_cpp listener
# Should print: "I heard: [Hello World: X]"