Three Reachy2 Simulations in Parallel: Docker Port Mapping and Isolation Guide
The Short Version
Three Reachy2 containers on the same host: each one keeps gRPC on port 50051 internally, and you remap those to host ports 50051, 50052, and 50053 using Docker’s -p flag. That handles SDK connectivity. The part people miss is ROS_DOMAIN_ID — without separate values per container, ROS 2’s DDS discovery layer lets the three simulations bleed into each other regardless of port isolation.
Why the Port Mapping Works This Way
Docker’s port mapping is one-directional: the container always listens on its fixed internal port (50051 for the Reachy2 SDK server), and the -p flag determines which host port routes traffic there.
For three containers:
-p 50051:50051— robot 1 atlocalhost:50051-p 50052:50051— robot 2 atlocalhost:50052-p 50053:50051— robot 3 atlocalhost:50053
You also need to offset the VNC display port (6080) and Jupyter port (8888) the same way. Any two containers sharing a host port will fail to start — Docker just refuses.
Docker Compose Setup
Running three separate docker run commands by hand is workable but fragile. A single docker-compose.yml brings everything up and down together and makes every port assignment obvious at a glance.
services:
reachy_1:
image: pollenrobotics/reachy2
platform: linux/amd64
container_name: reachy_sim_1
ports:
- '50051:50051'
- '6080:6080'
- '8888:8888'
environment:
- ROS_DOMAIN_ID=1
command: 'start_sdk_server:=true fake:=true mujoco:=true orbbec:=false start_rviz:=false'
reachy_2:
image: pollenrobotics/reachy2
platform: linux/amd64
container_name: reachy_sim_2
ports:
- '50052:50051'
- '6081:6080'
- '8889:8888'
environment:
- ROS_DOMAIN_ID=2
command: 'start_sdk_server:=true fake:=true mujoco:=true orbbec:=false start_rviz:=false'
reachy_3:
image: pollenrobotics/reachy2
platform: linux/amd64
container_name: reachy_sim_3
ports:
- '50053:50051'
- '6082:6080'
- '8890:8888'
environment:
- ROS_DOMAIN_ID=3
command: 'start_sdk_server:=true fake:=true mujoco:=true orbbec:=false start_rviz:=false'
Bring them all up with docker compose up -d. Tail all three logs at once with docker compose logs -f.
ROS_DOMAIN_ID: The Part That Trips People Up
ROS 2 uses DDS for discovery and message passing. By default, DDS uses multicast on the local network — and all containers on the same host share that network. Without separate domain IDs, nodes in container 1 will see and respond to DDS announcements from containers 2 and 3. The result is duplicate topic publishers, commands landing on the wrong robot, or simulations that mirror each other in unexpected ways.
ROS_DOMAIN_ID accepts an integer between 0 and 101. Assign a different one to each container and DDS keeps those namespaces completely separate. Any distinct values work — 1, 2, 3 is fine.
Connecting from the Python SDK
Once the containers are running, connect to each one using the host port you assigned:
from reachy2_sdk import ReachySDK
robot_a = ReachySDK(host='localhost', port=50051)
robot_b = ReachySDK(host='localhost', port=50052)
robot_c = ReachySDK(host='localhost', port=50053)
Each instance is fully independent. Check the current SDK docs for the exact constructor signature — it has shifted across versions, but this connection pattern stays consistent.
Unity: Three Prefabs, Three Ports
In Unity, each Reachy2DataClient prefab in your scene has a configurable gRPC target address. Set them to 50051, 50052, and 50053 respectively. If you copy-paste the prefab setup without changing the port, all three end up driving robot 1 — a small mistake that produces genuinely confusing behavior.
Resource Realities
Three simultaneous physics simulations are CPU-intensive. Keeping start_rviz:=false and not opening the VNC displays for all three reduces load noticeably. On a machine with fewer than 8 cores, expect simulation slowdown that can affect timing-sensitive control loops.
If DDS logs warnings about socket buffers or shared memory, that is a Linux kernel tuning issue. Raising net.core.rmem_max and net.core.wmem_max resolves most of them — the ROS 2 DDS tuning documentation covers the exact values.
Sources
- docs.pollen-robotics.com
- hub.docker.com
- github.com
- discourse.openrobotics.org
- docs.docker.com
- github.com
- docs.pollen-robotics.com
- grpc.io
