Isaac ROS Container¶
GPU-accelerated ROS 2 perception container for simulation mode. Built from a Dockerfile on top of NVIDIA's base image.
How It Works¶
The container is a self-contained ROS 2 environment with GPU access. It receives sensor data from Isaac Sim (running on the host) via DDS, runs GPU perception (SLAM, AprilTag, 3D mapping), and serves visualization over WebSocket.
flowchart LR
subgraph HOST["Host — Workstation"]
SIM["Isaac Sim<br/>(simulator)"]
end
subgraph CONTAINER["Container — isaac-ros-dev"]
PERC["GPU Perception<br/>AprilTag, SLAM, nvblox"]
FOX["Foxglove Bridge :8765<br/>H.264 NVENC republisher"]
end
BROWSER["Foxglove Studio<br/>(any browser)"]
SIM -- "UDP DDS" --> PERC
PERC -- "UDP DDS" --> SIM
FOX -- "WebSocket :8765" --> BROWSER
The container uses --network host so it shares the host's network stack — DDS topics flow freely between Isaac Sim and the container without any port mapping.
Quick Start¶
The container is managed entirely with docker-compose — no special CLI tools required.
# Start container
docker compose -f ~/workspaces/isaac_ros-dev/docker-compose.yml up -d
# Attach a shell
docker exec -it -u admin isaac_ros_dev_container bash
# Stop
docker compose -f ~/workspaces/isaac_ros-dev/docker-compose.yml down
Tip: Add an alias for quick shell access:
What's in the Base Image¶
NVIDIA provides a ~38.8 GB base image from NGC:
This includes:
- ROS 2 Jazzy — full desktop install (50+ packages)
- TensorRT — GPU-accelerated inference
- PyTorch — deep learning framework
- nav2 — ROS 2 navigation stack
- rviz2 — 3D visualization
- foxglove-bridge — WebSocket bridge for Foxglove Studio
- slam_toolbox — SLAM algorithms
- OpenCV — computer vision (custom NVIDIA build with CUDA)
- CUDA 13.0 dev tools — compiler, libraries, headers
- VPI, CV-CUDA, Triton — NVIDIA perception and inference libraries
What's NOT in the base (we add these):
- Standard ROS 2 apt repository (NVIDIA base only has their own repos)
- Isaac ROS perception packages (apriltag, cuVSLAM, nvblox)
- H.264 NVENC video transport for Foxglove
- ffmpeg with NVENC support
- Teleop packages (keyboard and joystick control)
- FastDDS configuration for host/container communication
What the Dockerfile Adds¶
The Dockerfile (~/workspaces/isaac_ros-dev/Dockerfile) adds a thin layer on top of the base. The base image is never modified — Docker layers are immutable.
Packages¶
The NVIDIA base image only includes NVIDIA's own apt repos. The Dockerfile first adds the standard ROS 2 community repo (packages.ros.org) and removes a broken yarn repo from the base image, then installs packages from both sources:
| Package | Source | What it does |
|---|---|---|
ros-jazzy-isaac-ros-apriltag |
NVIDIA | GPU-accelerated AprilTag detection and pose estimation |
ros-jazzy-isaac-ros-visual-slam |
NVIDIA | cuVSLAM — visual SLAM using stereo cameras on GPU |
ros-jazzy-isaac-ros-nvblox |
NVIDIA | GPU 3D reconstruction from depth cameras |
ros-jazzy-foxglove-compressed-video-transport |
NVIDIA | Encodes camera feeds as H.264 for Foxglove |
ros-jazzy-ffmpeg-encoder-decoder |
NVIDIA | ROS 2 wrapper around ffmpeg for NVENC encoding |
ros-jazzy-teleop-twist-keyboard |
NVIDIA | Keyboard-based robot control — publishes Twist to /cmd_vel |
ros-jazzy-teleop-twist-joy |
ROS 2 | Converts Joy messages to Twist — for Foxglove joystick control |
ffmpeg |
Ubuntu | CLI video tool (used by the encoder) |
After installing, rm -rf /var/lib/apt/lists/* deletes the apt package index cache (~50-100 MB) to keep the image smaller. Standard Docker practice.
FastDDS UDP-Only Transport¶
FastDDS SHM Transport Failure
The problem: ROS 2 uses DDS as its transport layer. On this system, Isaac Sim (host) uses FastDDS, which defaults to shared memory (SHM) when it detects two processes on the same machine. But Docker containers have isolated /dev/shm — even with --ipc host, FastDDS's SHM implementation fails to map memory across the host/container boundary. The result: ros2 topic list works (discovery uses UDP) but ros2 topic echo shows nothing (data transfer tries SHM and silently fails).
Fix: Force UDP Transport
An XML config at /etc/fastdds_no_shm.xml disables SHM and forces all traffic over UDP. The environment variable FASTRTPS_DEFAULT_PROFILES_FILE tells FastDDS to load this config.
This env var is set in two places:
/etc/profile.d/fastdds-fix.sh— picked up by login shells (when youdocker exec bash)- Explicitly in each entrypoint script — because the container's startup system sources scripts, not login shells, so profile.d doesn't apply
Sim vs real: This fix exists for simulation mode where Isaac Sim and the container share the same host. In real robot mode, the G1 uses CycloneDDS over Ethernet — a completely different DDS implementation on a different machine, so this config is irrelevant. However, forcing UDP is harmless in real mode too (UDP works fine over a network), so one image works for both scenarios without needing different configs.
Entrypoint Scripts¶
The NVIDIA base image has a startup system: when the container boots, it runs every .sh script in /usr/local/bin/scripts/entrypoint_additions/ in alphabetical order. The 50- and 60- prefixes control execution order.
| Script | What it does |
|---|---|
50-foxglove-bridge.sh |
Starts foxglove-bridge on port 8765 (WebSocket for Foxglove Studio) |
60-h264-republisher.sh |
Starts H.264 NVENC republisher (encodes raw camera topics for remote viewing) |
70-teleop-twist-joy.sh |
Starts teleop_node — converts /joy to /cmd_vel for Foxglove joystick control |
All scripts must explicitly set FASTRTPS_DEFAULT_PROFILES_FILE because entrypoint scripts are sourced, not run as login shells.
Admin User¶
A non-root admin user with passwordless sudo. Used when attaching to the container (docker exec -it -u admin).
Running Services¶
Foxglove Bridge¶
| Port | 8765 (WebSocket) |
| Connect | ws://workstation:8765 or ws://100.101.214.44:8765 (Tailscale) |
| Auto-starts | Yes, via entrypoint script |
Opens a WebSocket that Foxglove Studio connects to. Exposes all ROS 2 topics in real-time — camera feeds, SLAM maps, detections, joint states.
H.264 NVENC Republisher¶
| Subscribes to | /front_stereo_camera/left/image_rect_color (raw) |
| Publishes | /front_stereo_camera/left/compressed_video (H.264) |
| Auto-starts | Yes, via entrypoint script |
Encodes raw camera images using the GPU's hardware H.264 encoder (NVENC). Without this, streaming raw images over the network to Foxglove is too slow.
Note
H.265 does NOT work with Foxglove (browser can't decode keyframes). Use H.264 only.
Teleop Twist Joy¶
| Subscribes to | /joy (sensor_msgs/Joy) |
| Publishes | /cmd_vel (geometry_msgs/Twist) |
| Auto-starts | Yes, via entrypoint script |
Converts joystick input to velocity commands. Axis 1 (vertical) maps to linear.x (forward/back), axis 0 (horizontal) maps to angular.z (turn). Deadman switch is disabled — input goes straight to /cmd_vel.
Used with the foxglove-joystick extension in Foxglove Studio. Set the extension to Keyboard mode and publish to /joy.
Files¶
All in ~/workspaces/isaac_ros-dev/:
| File | Purpose |
|---|---|
Dockerfile |
Builds the image on top of NVIDIA base |
docker-compose.yml |
Launches the container with correct flags |
fastdds_no_shm.xml |
UDP-only DDS transport config |
50-foxglove-bridge.sh |
Foxglove auto-start entrypoint |
60-h264-republisher.sh |
H.264 republisher auto-start entrypoint |
70-teleop-twist-joy.sh |
Teleop Joy → Twist auto-start entrypoint |
Rebuilding the Image¶
The base image (38.8 GB) is cached — only the custom layer (~0.8 GB) rebuilds. Takes ~30 seconds.
Host Wrapper¶
~/bin/ros2¶
Runs ros2 commands inside the container from the host terminal:
docker exec isaac_ros_dev_container bash -c \
"export FASTRTPS_DEFAULT_PROFILES_FILE=/etc/fastdds_no_shm.xml && \
source /opt/ros/jazzy/setup.bash && \
ros2 $*"
Teleop (Keyboard Control)¶
Drive the robot from a terminal using teleop_twist_keyboard. It publishes geometry_msgs/Twist to /cmd_vel and sends zero velocity when you release keys.
# In a tmux session
docker exec -it isaac_ros_dev_container bash -c \
"source /opt/ros/jazzy/setup.bash && \
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp && \
export FASTRTPS_DEFAULT_PROFILES_FILE=/etc/fastdds_no_shm.xml && \
ros2 run teleop_twist_keyboard teleop_twist_keyboard"
Controls:
u i o i = forward k = stop
j k l , = backward q/z = speed up/down
m , . j/l = turn w/x = turn speed up/down
What's NOT in the Container¶
| Not included | Why |
|---|---|
| Unitree SDK / unitree_ros2 | Runs on the G1's Jetson Orin, not the workstation |
| System ROS 2 on host | Conflicts with Isaac Sim's bundled Python 3.11 rclpy |
| CycloneDDS config | Only needed for real robot mode (Orin handles this) |
Why Not isaac-ros-cli?¶
NVIDIA ships an apt package called isaac-ros-cli that provides isaac-ros activate and isaac-ros init commands for managing Isaac ROS containers. It was removed from this system (sudo apt remove isaac-ros-cli) and is not needed. Docker-compose replaces it entirely.
Problems with isaac-ros-cli:
- Raw
docker run— it launches containers withdocker runrather than compose, so there's no declarative config to version-control or reproduce. - Undocumented image key system — it layers customizations using an internal "image key" mechanism that isn't well documented. Understanding what ends up in the final image requires reading the CLI's source.
- Destroys customizations — running
isaac-ros activatewithout specifying the correct custom keys recreates the container from the stock NVIDIA base image, silently losing any packages or configs you added. - Redundant configs — it ships its own Dockerfiles and FastDDS configs in
/etc/isaac-ros-cli/, duplicating what's already managed in~/workspaces/isaac_ros-dev/.
The docker-compose approach at ~/workspaces/isaac_ros-dev/ gives full control over the image, explicit configuration in version-controlled files, and predictable up/down lifecycle management.
Troubleshooting¶
Topics visible but no data flowing
FastDDS SHM issue. Check that the XML config exists and the env var is set:
docker exec isaac_ros_dev_container bash -c \
"cat /etc/fastdds_no_shm.xml && echo '---' && echo \$FASTRTPS_DEFAULT_PROFILES_FILE"
If missing, the Dockerfile needs rebuilding — the config is baked into the image.
Foxglove says \"Check that WebSocket server is reachable\"
foxglove-bridge isn't running. Check and restart:
Container missing packages after restart
This happens when a container is recreated from the stock NVIDIA base image instead of the custom isaac-ros-dev:latest image. A common cause was isaac-ros activate (from the isaac-ros-cli package), which silently recreates containers from base without custom layers. That tool has been removed — see Why Not isaac-ros-cli? above. Always use docker compose up -d to manage the container.
Camera feed flickering between two views in Foxglove
If the video stream appears to alternate between two slightly offset perspectives (like flipping between left and right stereo cameras), check for multiple Isaac Sim instances publishing to the same ROS 2 topics:
Two instances will interleave frames on the same topic, causing the flickering. Kill the extra instance and leave only one running. This commonly happens when a headless instance was left running in a tmux session and a second GUI instance was launched separately.