docker-ros2-development について
Docker 上で ROS2 システムを開発するための実践的なスキル資料です。ビルド、コンテナネットワーク、ハードウェアアクセス、GUI ツール、CI ワークフローを扱います。
主な機能
- 01ROS2 のマルチステージ Dockerfile
- 02Docker Compose のサービス構成
- 03DDS ディスカバリー設定
- 04GPU と USB のパススルー
- 05X11 と Wayland の転送
- 06Colcon ビルドキャッシュ
活用シーン
- 01ROS2 ワークスペースのコンテナイメージ作成
- 02コンテナ間の DDS ディスカバリー障害の調査
- 03認識ノードへの GPU アクセス設定
- 04Docker ベースの ROS2 CI ビルド準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ arpitg1304/ robotics-agent-skills --skill docker-ros2-developmentインストール後、Claude Code で/docker-ros2-developmentを入力して読み込まれたか確認します。
詳細
Docker ベースの ROS2 開発スキル
このスキルを使う場面
- colcon ビルドを行う ROS2 ワークスペース用の Dockerfile を作成する
- 複数コンテナーのロボットシステム用に docker-compose を設定する
- コンテナー間の DDS 検出の失敗(CycloneDDS、FastDDS)をデバッグする
- 認識ノード用に NVIDIA Container Toolkit で GPU パススルーを設定する
- rviz2 と rqt ツール用に X11 または Wayland のディスプレイを転送する
- カメラ、LiDAR、シリアルデバイス用に USB デバイスのパススルーを管理する
- Docker ベースの ROS2 ビルドとテストランナーを使って CI/CD パイプラインを構築する
- ROS2 拡張機能を備えた VS Code 用の devcontainer 設定を作成する
- colcon ワークスペースのビルド向けに Docker レイヤーキャッシュを最適化する
- マルチステージビルドを使って開発用とデプロイ用のコンテナー戦略を設計する
ROS2 Docker イメージの階層
公式の OSRF イメージは階層構造になっています。依存関係を満たす最小のベースを常に選んでください。
┌──────────────────────────────────────────────────────────────────┐
│ ros:<distro>-desktop-full (~3.5 GB) │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ ros:<distro>-desktop (~2.8 GB) │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ ros:<distro>-perception (~2.2 GB) │ │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │ │
│ │ │ │ ros:<distro>-ros-base (~1.1 GB) │ │ │ │
│ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ │
│ │ │ │ │ ros:<distro>-ros-core (~700 MB) │ │ │ │ │
│ │ │ │ └──────────────────────────────────────────┘ │ │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
| イメージタグ | ベース OS | サイズ | 内容 | 用途 |
|---|---|---|---|---|
ros:humble-ros-core | Ubuntu 22.04 | ~700 MB | rclcpp、rclpy、rosout、launch | 単一ノード向けの最小ランタイム |
ros:humble-ros-base | Ubuntu 22.04 | ~1.1 GB | ros-core + common_interfaces、rosbag2 | ほとんどの本番環境へのデプロイ |
ros:humble-perception | Ubuntu 22.04 | ~2.2 GB | ros-base + image_transport、cv_bridge、PCL | カメラ/LiDAR の認識パイプライン |
ros:humble-desktop | Ubuntu 22.04 | ~2.8 GB | perception + rviz2、rqt、デモ | GUI ツールを使った開発 |
ros:jazzy-ros-core | Ubuntu 24.04 | ~750 MB | rclcpp、rclpy、rosout、launch | 最小ランタイム(Jazzy/Noble) |
ros:jazzy-ros-base | Ubuntu 24.04 | ~1.2 GB | ros-core + common_interfaces、rosbag2 | 本番環境へのデプロイ(Jazzy) |
ROS2 用マルチステージ Dockerfile
開発ステージ
開発ステージには、対話的な利用に必要なビルドツール、デバッガー、エディターのサポートが含まれます。
FROM ros:humble-desktop AS dev
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential cmake gdb python3-pip \
python3-colcon-common-extensions python3-rosdep \
ros-humble-ament-lint-auto ros-humble-ament-cmake-pytest \
ccache \
&& rm -rf /var/lib/apt/lists/*
ENV CCACHE_DIR=/ccache
ENV CC="ccache gcc"
ENV CXX="ccache g++"
ビルドステージ
依存関係の解決時にキャッシュヒットを最大化するため、src/ と package.xml ファイルのみをコピーします。
FROM ros:humble-ros-base AS build
RUN apt-get update && apt-get install -y --no-install-recommends \
python3-colcon-common-extensions python3-rosdep \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /ros2_ws
# Copy package manifests first for dependency caching
COPY src/my_pkg/package.xml src/my_pkg/package.xml
RUN . /opt/ros/humble/setup.sh && apt-get update && \
rosdep install --from-paths src --ignore-src -r -y && \
rm -rf /var/lib/apt/lists/*
# Source changes invalidate only this layer and below
COPY src/ src/
RUN . /opt/ros/humble/setup.sh && \
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release \
--event-handlers console_direct+
ランタイムステージ
ビルド済みの install スペースとランタイム依存関係のみを含みます。コンパイラーもソースコードも含みません。
FROM ros:humble-ros-core AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
python3-yaml ros-humble-rmw-cyclonedds-cpp \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /ros2_ws/install /ros2_ws/install
RUN groupadd -r rosuser && useradd -r -g rosuser -m rosuser
USER rosuser
COPY ros_entrypoint.sh /ros_entrypoint.sh
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["ros2", "launch", "my_pkg", "bringup.launch.py"]
完全なマルチステージ Dockerfile
# syntax=docker/dockerfile:1
# Usage:
# docker build --target dev -t my_robot:dev .
# docker build --target runtime -t my_robot:latest .
ARG ROS_DISTRO=humble
ARG BASE_IMAGE=ros:${ROS_DISTRO}-ros-base
# Stage 1: Dependency base — install apt and rosdep deps
FROM ${BASE_IMAGE} AS deps
RUN apt-get update && apt-get install -y --no-install-recommends \
python3-colcon-common-extensions python3-rosdep \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /ros2_ws
# Copy only package.xml files for rosdep resolution (maximizes cache reuse)
COPY src/my_robot_bringup/package.xml src/my_robot_bringup/package.xml
COPY src/my_robot_perception/package.xml src/my_robot_perception/package.xml
COPY src/my_robot_msgs/package.xml src/my_robot_msgs/package.xml
COPY src/my_robot_navigation/package.xml src/my_robot_navigation/package.xml
RUN . /opt/ros/${ROS_DISTRO}/setup.sh && \
apt-get update && \
rosdep install --from-paths src --ignore-src -r -y && \
rm -rf /var/lib/apt/lists/*
# Stage 2: Development — full dev environment
FROM deps AS dev
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential gdb valgrind ccache python3-pip python3-pytest \
ros-${ROS_DISTRO}-ament-lint-auto \
ros-${ROS_DISTRO}-launch-testing-ament-cmake \
ros-${ROS_DISTRO}-rviz2 ros-${ROS_DISTRO}-rqt-graph \
&& rm -rf /var/lib/apt/lists/*
ENV CCACHE_DIR=/ccache CC="ccache gcc" CXX="ccache g++"
COPY src/ src/
COPY ros_entrypoint.sh /ros_entrypoint.sh
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]
# Stage 3: Build — compile workspace
FROM deps AS build
COPY src/ src/
RUN . /opt/ros/${ROS_DISTRO}/setup.sh && \
colcon build \
--cmake-args -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF \
--event-handlers console_direct+ \
--parallel-workers $(nproc)
# Stage 4: Runtime — minimal production image
FROM ros:${ROS_DISTRO}-ros-core AS runtime
ARG ROS_DISTRO=humble
RUN apt-get update && apt-get install -y --no-install-recommends \
python3-yaml ros-${ROS_DISTRO}-rmw-cyclonedds-cpp \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /ros2_ws/install /ros2_ws/install
RUN groupadd -r rosuser && useradd -r -g rosuser -m -s /bin/bash rosuser
USER rosuser
ENV RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
COPY ros_entrypoint.sh /ros_entrypoint.sh
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["ros2", "launch", "my_robot_bringup", "robot.launch.py"]
開発ステージとランタイムステージの両方で使用するエントリーポイントスクリプトです。
#!/bin/bash
set -e
source /opt/ros/${ROS_DISTRO}/setup.bash
if [ -f /ros2_ws/install/setup.bash ]; then
source /ros2_ws/install/setup.bash
fi
exec "$@"
複数コンテナーの ROS2 システム向け Docker Compose
基本的な複数コンテナー構成
各 ROS2 サブシステムを独自のコンテナーで実行することで、プロセスを分離し、独立したスケーリングとサービスごとのリソース制限を実現します。
# docker-compose.yml
version: "3.8"
x-ros-common: &ros-common
environment:
- ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-0}
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
- CYCLONEDDS_URI=file:///cyclonedds.xml
volumes:
- ./config/cyclonedds.xml:/cyclonedds.xml:ro
- /dev/shm:/dev/shm
network_mode: host
restart: unless-stopped
services:
rosbridge:
<<: *ros-common
image: my_robot:latest
command: ros2 launch rosbridge_server rosbridge_websocket_launch.xml port:=9090
perception:
<<: *ros-common
image: my_robot_perception:latest
command: ros2 launch my_robot_perception perception.launch.py
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
devices:
- /dev/video0:/dev/video0 # USB camera passthrough
navigation:
<<: *ros-common
image: my_robot_navigation:latest
command: >
ros2 launch my_robot_navigation navigation.launch.py
use_sim_time:=false map:=/maps/warehouse.yaml
volumes:
- ./maps:/maps:ro
driver:
<<: *ros-common
image: my_robot_driver:latest
command: ros2 launch my_robot_driver driver.launch.py
devices:
- /dev/ttyUSB0:/dev/ttyUSB0 # Serial motor controller
- /dev/ttyACM0:/dev/ttyACM0 # IMU over USB-serial
group_add:
- dialout
ヘルスチェックを使ったサービス依存関係
services:
driver:
<<: *ros-common
image: my_robot_driver:latest
healthcheck:
test: ["CMD", "bash", "-c",
"source /opt/ros/humble/setup.bash && ros2 topic list | grep -q /joint_states"]
interval: 5s
timeout: 10s
retries: 5
start_period: 15s
navigation:
<<: *ros-common
image: my_robot_navigation:latest
depends_on:
driver:
condition: service_healthy # Wait for driver topics
perception:
<<: *ros-common
image: my_robot_perception:latest
depends_on:
driver:
condition: service_healthy # Camera driver must be ready
開発用とデプロイ用のプロファイル
services:
driver:
<<: *ros-common
image: my_robot_driver:latest
command: ros2 launch my_robot_driver driver.launch.py
rviz:
<<: *ros-common
profiles: ["dev"]
image: my_robot:dev
command: ros2 run rviz2 rviz2 -d /rviz/config.rviz
environment:
- DISPLAY=${DISPLAY}
- QT_X11_NO_MITSHM=1
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix:rw
rosbag_record:
<<: *ros-common
profiles: ["dev"]
image: my_robot:dev
command: ros2 bag record -a --storage sqlite3 --max-bag-duration 300 -o /bags/session
volumes:
- ./bags:/bags
watchdog:
<<: *ros-common
profiles: ["deploy"]
image: my_robot:latest
command: ros2 launch my_robot_bringup watchdog.launch.py
restart: always
docker compose --profile dev up # Dev tools (rviz, rosbag)
docker compose --profile deploy up -d # Production (watchdog, no GUI)
コンテナー間の DDS 検出
コンテナー間のユニキャスト用 CycloneDDS XML 設定
コンテナーがブリッジネットワーク(マルチキャストなし)を使う場合は、ユニキャストのピアリストを明示的に設定します。
<?xml version="1.0" encoding="UTF-8"?>
<CycloneDDS xmlns="https://cdds.io/config">
<Domain>
<General>
<Interfaces>
<NetworkInterface autodetermine="true" priority="default"/>
</Interfaces>
<AllowMulticast>false</AllowMulticast>
</General>
<Discovery>
<Peers>
<Peer address="perception"/>
<Peer address="navigation"/>
<Peer address="driver"/>
<Peer address="rosbridge"/>
</Peers>
<ParticipantIndex>auto</ParticipantIndex>
<MaxAutoParticipantIndex>120</MaxAutoParticipantIndex>
</Discovery>
<Internal>
<SocketReceiveBufferSize min="10MB"/>
</Internal>
</Domain>
</CycloneDDS>
FastDDS XML 設定
<?xml version="1.0" encoding="UTF-8"?>
<dds xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<profiles>
<participant profile_name="docker_participant" is_default_profile="true">
<rtps>
<builtin>
<discovery_config>
<discoveryProtocol>SIMPLE</discoveryProtocol>
<leaseDuration><sec>10</sec></leaseDuration>
</discovery_config>
<initialPeersList>
<locator>
<udpv4><address>perception</address><port>7412</port></udpv4>
</locator>
<locator>
<udpv4><address>navigation</address><port>7412</port></udpv4>
</locator>
<locator>
<udpv4><address>driver</address><port>7412</port></udpv4>
</locator>
</initialPeersList>
</builtin>
</rtps>
</participant>
</profiles>
</dds>
compose でマウントして有効化します。
# CycloneDDS
environment:
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
- CYCLONEDDS_URI=file:///cyclonedds.xml
volumes:
- ./config/cyclonedds.xml:/cyclonedds.xml:ro
# FastDDS
environment:
- RMW_IMPLEMENTATION=rmw_fastrtps_cpp
- FASTRTPS_DEFAULT_PROFILES_FILE=/fastdds.xml
volumes:
- ./config/fastdds.xml:/fastdds.xml:ro
Docker での共有メモリトランスポート
DDS の共有メモリ(ゼロコピー)には、コンテナー間での /dev/shm の共有が必要です。画像や点群などの大きなメッセージで最高のスループットを実現します。
services:
perception:
shm_size: "512m" # Default 64 MB is too small for image topics
volumes:
- /dev/shm:/dev/shm # Share host shm for inter-container zero-copy
<CycloneDDS xmlns="https://cdds.io/config">
<Domain>
<SharedMemory>
<Enable>true</Enable>
</SharedMemory>
</Domain>
</CycloneDDS>
制約:通信するすべてのコンテナーが /dev/shm を共有するか、ipc: host を使う必要があります。スコープを限定して共有するには、1 つのコンテナーで --ipc=shareable を使い、他のコンテナーでは --ipc=container:<name> を使います。
ネットワークモードと ROS2 への影響
ホストネットワーク
services:
my_node:
network_mode: host # Shares host network namespace; DDS multicast works natively
ブリッジネットワーク(デフォルト)
services:
my_node:
networks: [ros_net]
networks:
ros_net:
driver: bridge # DDS multicast blocked; requires unicast peer config
Macvlan ネットワーク
networks:
ros_macvlan:
driver: macvlan
driver_opts:
parent: eth0
ipam:
config:
- subnet: 192.168.1.0/24
gateway: 192.168.1.1
services:
my_node:
networks:
ros_macvlan:
ipv4_address: 192.168.1.50 # Real LAN IP; DDS multicast works natively
判断表
| 要素 | ホスト | ブリッジ | Macvlan |
|---|---|---|---|
| DDS の検出 | ネイティブで動作 | ユニキャストピアが必要 | ネイティブで動作 |
| ネットワーク分離 | なし | 完全に分離 | LAN レベルで分離 |
| ポートの競合 | あり(ホストポート) | なし(ポートマッピング) | なし(一意の IP) |
| パフォーマンス | ネイティブ | わずかなオーバーヘッド | ほぼネイティブ |
| 複数ホストのサポート | なし | オーバーレイネットワークを使用 | あり(同一 LAN) |
| 使用する場面 | 開発、単一ホスト | CI/CD、マルチテナント | LAN 上の複数ロボット |
認識処理向け GPU パススルー
NVIDIA Container Toolkit のセットアップ
# Install NVIDIA Container Toolkit on the host
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
| sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
| sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
deploy.resources を使用した Compose 設定
services:
perception:
image: my_robot_perception:latest
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1 # Number of GPUs (or "all")
capabilities: [gpu]
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility,video
shm_size: "1g" # Large shm for GPU<->CPU transfers
CUDA が必要な Dockerfile では、NVIDIA のベースイメージを使用し、その上に ROS2 をインストールします。
FROM nvidia/cuda:12.2.0-cudnn8-runtime-ubuntu22.04 AS perception-base
RUN apt-get update && apt-get install -y --no-install-recommends \
curl gnupg2 lsb-release \
&& curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \
http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/ros2.list \
&& apt-get update && apt-get install -y --no-install-recommends \
ros-humble-ros-base ros-humble-cv-bridge ros-humble-image-transport \
&& rm -rf /var/lib/apt/lists/*
検証
docker compose exec perception bash -c '
nvidia-smi
python3 -c "import torch; print(f\"CUDA available: {torch.cuda.is_available()}\")"
'
ディスプレイ転送
X11 転送
services:
rviz:
image: my_robot:dev
command: ros2 run rviz2 rviz2
environment:
- DISPLAY=${DISPLAY:-:0} # Forward host display
- QT_X11_NO_MITSHM=1 # Disable MIT-SHM (crashes in Docker)
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix:rw # X11 socket
- ${HOME}/.Xauthority:/root/.Xauthority:ro # Auth cookie
network_mode: host
# Allow local Docker containers to access the X server
xhost +local:docker
# More secure variant:
xhost +SI:localuser:$(whoami)
Wayland 転送
services:
rviz:
image: my_robot:dev
command: ros2 run rviz2 rviz2
environment:
- WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-wayland-0}
- XDG_RUNTIME_DIR=/run/user/1000
- QT_QPA_PLATFORM=wayland
volumes:
- ${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY}:/run/user/1000/${WAYLAND_DISPLAY}:rw
ヘッドレスレンダリング
CI/CD や、物理ディスプレイのないリモートマシンの場合:
# Run rviz2 headless with Xvfb for screenshot capture or testing
docker run --rm my_robot:dev bash -c '
apt-get update && apt-get install -y xvfb mesa-utils &&
Xvfb :99 -screen 0 1920x1080x24 &
export DISPLAY=:99
source /opt/ros/humble/setup.bash
ros2 run rviz2 rviz2 -d /config/test.rviz --screenshot /output/frame.png
'
ボリュームマウントとワークスペースのオーバーレイ
開発用のソースマウント
開発中は src/ のみをマウントします。バインドマウントのパフォーマンス問題を避けるため、colcon が build/、install/、log/ に書き込めるよう、これらは名前付きボリューム内に配置します。
# BAD: mounting entire workspace — build artifacts on bind mount are slow
# volumes:
# - ./my_ros2_ws:/ros2_ws
# GOOD: mount only source, use named volumes for build artifacts
services:
dev:
image: my_robot:dev
volumes:
- ./src:/ros2_ws/src:rw # Source code (bind mount)
- build_vol:/ros2_ws/build # Build artifacts (named volume)
- install_vol:/ros2_ws/install # Install space (named volume)
- log_vol:/ros2_ws/log # Log output (named volume)
working_dir: /ros2_ws
volumes:
build_vol:
install_vol:
log_vol:
ccache によるキャッシュ
コンテナの再ビルド後も ccache を保持して、C++ のコンパイルを高速化します。
services:
dev:
volumes:
- ccache_vol:/ccache
environment:
- CCACHE_DIR=/ccache
- CCACHE_MAXSIZE=2G
- CC=ccache gcc
- CXX=ccache g++
volumes:
ccache_vol:
Docker での ROS ワークスペースのオーバーレイ
上流パッケージはキャッシュし、カスタムパッケージのみを再ビルドします。
# Stage 1: upstream dependencies (rarely changes)
FROM ros:humble-ros-base AS upstream
RUN apt-get update && apt-get install -y --no-install-recommends \
ros-humble-nav2-bringup ros-humble-slam-toolbox \
ros-humble-robot-localization \
&& rm -rf /var/lib/apt/lists/*
# Stage 2: custom packages overlay on top
FROM upstream AS workspace
WORKDIR /ros2_ws
COPY src/ src/
RUN . /opt/ros/humble/setup.sh && colcon build --symlink-install
# install/setup.bash automatically sources /opt/ros/humble as underlay
USB デバイスのパススルー
カメラとシリアルデバイス
services:
camera_driver:
image: my_robot_driver:latest
devices:
- /dev/video0:/dev/video0 # USB camera (V4L2)
- /dev/video1:/dev/video1
group_add:
- video # Access /dev/videoN without root
motor_driver:
image: my_robot_driver:latest
devices:
- /dev/ttyUSB0:/dev/ttyUSB0 # USB-serial motor controller
- /dev/ttyACM0:/dev/ttyACM0 # Arduino/Teensy
group_add:
- dialout # Access serial ports without root
コンテナ内の Udev ルール
ホスト上で安定したデバイスのシンボリックリンクを作成し、USB の列挙順序にかかわらず、コンテナのパスが一定になるようにします。
# /etc/udev/rules.d/99-robot-devices.rules (host-side)
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="robot/motor_controller"
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="robot/lidar"
SUBSYSTEM=="video4linux", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0825", SYMLINK+="robot/camera"
sudo udevadm control --reload-rules && sudo udevadm trigger
services:
driver:
devices:
- /dev/robot/motor_controller:/dev/ttyMOTOR # Stable symlink
- /dev/robot/lidar:/dev/ttyLIDAR
- /dev/robot/camera:/dev/video0
デバイスの動的な接続
コンテナの起動後に接続するデバイスの場合:
services:
driver:
# Option 1: privileged (use only when necessary)
privileged: true
volumes:
- /dev:/dev
# Option 2: cgroup device rules (more secure)
# device_cgroup_rules:
# - 'c 188:* rmw' # USB-serial (major 188)
# - 'c 81:* rmw' # Video devices (major 81)
Dev Container の設定
// .devcontainer/devcontainer.json
{
"name": "ROS2 Humble Dev",
"build": {
"dockerfile": "../Dockerfile",
"target": "dev",
"args": { "ROS_DISTRO": "humble" }
},
"runArgs": [
"--network=host", "--ipc=host", "--pid=host",
"--privileged", "--gpus", "all",
"-e", "DISPLAY=${localEnv:DISPLAY}",
"-e", "QT_X11_NO_MITSHM=1",
"-v", "/tmp/.X11-unix:/tmp/.X11-unix:rw",
"-v", "/dev:/dev"
],
"workspaceMount": "source=${localWorkspaceFolder},target=/ros2_ws/src,type=bind",
"workspaceFolder": "/ros2_ws",
"mounts": [
"source=ros2-build-vol,target=/ros2_ws/build,type=volume",
"source=ros2-install-vol,target=/ros2_ws/install,type=volume",
"source=ros2-log-vol,target=/ros2_ws/log,type=volume",
"source=ros2-ccache-vol,target=/ccache,type=volume"
],
"containerEnv": {
"ROS_DISTRO": "humble",
"RMW_IMPLEMENTATION": "rmw_cyclonedds_cpp",
"CCACHE_DIR": "/ccache",
"RCUTILS_COLORIZED_OUTPUT": "1"
},
"customizations": {
"vscode": {
"extensions": [
"ms-iot.vscode-ros",
"ms-vscode.cpptools",
"ms-python.python",
"ms-vscode.cmake-tools",
"smilerobotics.urdf",
"redhat.vscode-xml",
"redhat.vscode-yaml"
],
"settings": {
"ros.distro": "humble",
"python.defaultInterpreterPath": "/usr/bin/python3",
"C_Cpp.default.compileCommands": "/ros2_ws/build/compile_commands.json",
"cmake.configureOnOpen": false
}
}
},
"postCreateCommand": "sudo apt-get update && rosdep update && rosdep install --from-paths src --ignore-src -r -y",
"remoteUser": "rosuser"
}
Docker による CI/CD
GitHub Actions のワークフロー
# .github/workflows/ros2-docker-ci.yml
name: ROS2 Docker CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-test:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
ros_distro: [humble, jazzy]
include:
- ros_distro: humble
ubuntu: "22.04"
- ros_distro: jazzy
ubuntu: "24.04"
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ matrix.ros_distro }}-${{ hashFiles('src/**/package.xml') }}
restore-keys: ${{ runner.os }}-buildx-${{ matrix.ros_distro }}-
- name: Build and test
run: |
docker build --target dev \
--build-arg ROS_DISTRO=${{ matrix.ros_distro }} \
-t test-image:${{ matrix.ros_distro }} .
docker run --rm test-image:${{ matrix.ros_distro }} bash -c '
source /opt/ros/${{ matrix.ros_distro }}/setup.bash &&
cd /ros2_ws &&
colcon build --cmake-args -DBUILD_TESTING=ON &&
colcon test --event-handlers console_direct+ &&
colcon test-result --verbose'
- name: Push runtime image
if: github.ref == 'refs/heads/main'
uses: docker/build-push-action@v5
with:
context: .
target: runtime
build-args: ROS_DISTRO=${{ matrix.ros_distro }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ matrix.ros_distro }}-latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ matrix.ros_distro }}-${{ github.sha }}
push: true
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Rotate cache
run: rm -rf /tmp/.buildx-cache && mv /tmp/.buildx-cache-new /tmp/.buildx-cache
レイヤーのキャッシュ
変更頻度の低いものから高いものの順に、Dockerfile の命令を並べます。
1. Base image (ros:humble-ros-base) — changes on distro upgrade
2. System apt packages — changes on new dependency
3. rosdep install (from package.xml) — changes on new ROS dep
4. COPY src/ src/ — changes on every code edit
5. colcon build — rebuilds on source change
ディストリビューション間のビルドマトリックス
strategy:
matrix:
ros_distro: [humble, iron, jazzy, rolling]
rmw: [rmw_cyclonedds_cpp, rmw_fastrtps_cpp]
exclude:
- ros_distro: iron # Iron EOL — skip
rmw: rmw_fastrtps_cpp
Docker での ROS2 のアンチパターン
1. すべてを 1 つのコンテナで実行する
問題: 認識処理、ナビゲーション、プランニング、ドライバーを 1 つのコンテナにまとめると、コンテナ化の目的が損なわれます。1 つのサブシステムでクラッシュが発生すると、すべてが停止します。
解決策: サブシステムごとにサービスを分割します。docker-compose を使用してオーケストレーションします。
# BAD: monolithic container
services:
robot:
image: my_robot:latest
command: ros2 launch my_robot everything.launch.py
# GOOD: one container per subsystem
services:
perception:
image: my_robot_perception:latest
command: ros2 launch my_robot_perception perception.launch.py
navigation:
image: my_robot_navigation:latest
command: ros2 launch my_robot_navigation navigation.launch.py
driver:
image: my_robot_driver:latest
command: ros2 launch my_robot_driver driver.launch.py
2. DDS の設定なしでブリッジネットワークを使用する
問題: DDS は、既定で検出にマルチキャストを使用します。Docker のブリッジネットワークはマルチキャストを転送しません。異なるコンテナ内のノードは互いを検出できません。
解決策: network_mode: host を使用するか、DDS のユニキャストピアを明示的に設定します。
# BAD: bridge network with no DDS config
services:
node_a:
networks: [ros_net]
node_b:
networks: [ros_net]
# GOOD: host networking (simplest)
services:
node_a:
network_mode: host
node_b:
network_mode: host
# GOOD: bridge with CycloneDDS unicast peers
services:
node_a:
networks: [ros_net]
environment:
- RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
- CYCLONEDDS_URI=file:///cyclonedds.xml
volumes:
- ./cyclonedds.xml:/cyclonedds.xml:ro
3. ランタイムイメージでパッケージをビルドする
問題: ランタイムイメージにコンパイラーやビルドツールをインストールすると、イメージのサイズが 1~2 GB 増え、攻撃対象領域も広がります。
解決策: マルチステージビルドを使用します。ビルドステージでコンパイルし、ランタイムには install スペースのみをコピーします。
# BAD: build tools in runtime image (2.5 GB)
FROM ros:humble-ros-base
RUN apt-get update && apt-get install -y build-essential python3-colcon-common-extensions
COPY src/ /ros2_ws/src/
RUN cd /ros2_ws && colcon build
CMD ["ros2", "launch", "my_pkg", "bringup.launch.py"]
# GOOD: multi-stage build (800 MB)
FROM ros:humble-ros-base AS build
RUN apt-get update && apt-get install -y python3-colcon-common-extensions
COPY src/ /ros2_ws/src/
RUN cd /ros2_ws && . /opt/ros/humble/setup.sh && colcon build
FROM ros:humble-ros-core AS runtime
COPY --from=build /ros2_ws/install /ros2_ws/install
CMD ["ros2", "launch", "my_pkg", "bringup.launch.py"]
4. ワークスペース全体をボリュームとしてマウントする
問題: ワークスペース全体をマウントすると、colcon は build/、install/、log/ にバインドマウント経由で書き込みます。macOS/Windows の Docker Desktop では、バインドマウントの I/O が 10~50 倍遅くなります。2 分かかるビルドが 20 分以上かかることもあります。
解決策: バインドマウントするのは src/ のみとします。ビルド成果物には名前付きボリュームを使用します。
# BAD:
volumes:
- ./my_ros2_ws:/ros2_ws
# GOOD:
volumes:
- ./my_ros2_ws/src:/ros2_ws/src
- build_vol:/ros2_ws/build
- install_vol:/ros2_ws/install
- log_vol:/ros2_ws/log
5. コンテナを root で実行する
問題: コンテナ内で root として実行するのはセキュリティ上のリスクです。侵害された場合、攻撃者はマウントされたボリュームやデバイスに対する root 権限を取得します。
解決策: 適切なグループに所属する、root 以外のユーザーを作成します。
# BAD:
FROM ros:humble-ros-base
COPY --from=build /ros2_ws/install /ros2_ws/install
CMD ["ros2", "launch", "my_pkg", "bringup.launch.py"]
# GOOD:
FROM ros:humble-ros-base
RUN groupadd -r rosuser && \
useradd -r -g rosuser -G video,dialout -m -s /bin/bash rosuser
COPY --from=build --chown=rosuser:rosuser /ros2_ws/install /ros2_ws/install
USER rosuser
CMD ["ros2", "launch", "my_pkg", "bringup.launch.py"]
6. Dockerfile でレイヤーキャッシュの順序を無視する
問題: rosdep install の前に COPY src/ . を配置すると、ソースに変更があるたびに依存関係のキャッシュが無効になります。ビルドのたびに apt パッケージがすべて再ダウンロードされます。
修正: まず package.xml ファイルだけをコピーして依存関係をインストールし、その後にソースをコピーします。
# BAD: source copy before rosdep
COPY src/ /ros2_ws/src/
RUN rosdep install --from-paths src --ignore-src -r -y
RUN colcon build
# GOOD: package.xml first, then rosdep, then source
COPY src/my_pkg/package.xml /ros2_ws/src/my_pkg/package.xml
RUN . /opt/ros/humble/setup.sh && rosdep install --from-paths src --ignore-src -r -y
COPY src/ /ros2_ws/src/
RUN . /opt/ros/humble/setup.sh && colcon build
7. ROS_DOMAIN_ID をハードコードする
問題: ROS_DOMAIN_ID=42 をハードコードすると、複数のロボットや開発者が同じネットワークを共有する際に競合が発生します。同じドメイン ID を持つ 2 台のロボットは、相互に通信してしまいます。
修正: デフォルト値を持つ環境変数を使用します。デプロイ時にドメイン ID を設定します。
# BAD:
environment:
- ROS_DOMAIN_ID=42
# GOOD:
environment:
- ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-0}
ROS_DOMAIN_ID=1 docker compose up -d # Robot 1
ROS_DOMAIN_ID=2 docker compose up -d # Robot 2
8. エントリーポイントで setup.bash を source し忘れる
問題: CMD は ros2 launch ... を実行しますが、シェルで ROS2 のセットアップファイルが source されていません。ros2: command not found というエラーが発生します。
修正: コマンドを実行する前に underlay と overlay を source するエントリーポイントスクリプトを使用します。
# BAD: no sourcing
FROM ros:humble-ros-core
COPY --from=build /ros2_ws/install /ros2_ws/install
CMD ["ros2", "launch", "my_pkg", "bringup.launch.py"]
# GOOD: entrypoint script handles sourcing
FROM ros:humble-ros-core
COPY --from=build /ros2_ws/install /ros2_ws/install
COPY ros_entrypoint.sh /ros_entrypoint.sh
RUN chmod +x /ros_entrypoint.sh
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["ros2", "launch", "my_pkg", "bringup.launch.py"]
#!/bin/bash
# ros_entrypoint.sh
set -e
source /opt/ros/${ROS_DISTRO}/setup.bash
if [ -f /ros2_ws/install/setup.bash ]; then
source /ros2_ws/install/setup.bash
fi
exec "$@"
Docker ROS2 デプロイのチェックリスト
- マルチステージビルド -- 開発、ビルド、ランタイムの各ステージを分け、本番用イメージにコンパイラーやビルドツールが含まれないようにします
- 非 root ユーザー -- 特権モードを使わず、必要なグループ(video、dialout)のみに所属する専用の
rosuserを作成します - DDS の設定 -- ホストネットワークを使用しない場合は、ピアリストを明示した
cyclonedds.xmlまたはfastdds.xmlを含めます - ヘルスチェック -- すべてのサービスに、ノードが実行中で、想定されるトピックをパブリッシュしていることを確認するヘルスチェックを設定します
- リソース制限 -- リソース不足を防ぐため、各サービスに
mem_limit、cpus、GPU の予約を設定します - 再起動ポリシー -- すべてのサービスには
restart: unless-stopped、重要なドライバーとウォッチドッグにはrestart: alwaysを使用します - ログ管理 -- ROS2 のログ出力によるディスク容量の枯渇を防ぐため、Docker のログドライバーを設定します(最大サイズと最大ファイル数を指定した json-file)
- 環境変数の注入 --
ROS_DOMAIN_ID、DDS 設定パス、デバイスのマッピングはハードコードせず、.envファイルまたはオーケストレーターのシークレットを使用します - 共有メモリ --
shm_sizeを 256 MB 以上(画像トピックの場合は 512 MB)に設定し、帯域幅の大きいトピックでは DDS の共有メモリトランスポートを設定します - デバイスの安定性 -- ホストで udev ルールを使用して安定した
/dev/robot/*シンボリックリンクを作成し、Compose のデバイスマッピングでそれらを参照します - イメージのバージョン管理 -- イメージには
latestとコミット SHA またはセマンティックバージョンの両方のタグを付けます。本番環境にバージョン指定のないlatestをデプロイしてはいけません - バックアップとロールバック -- デプロイに失敗した場合、イメージタグを戻してロールバックできるよう、前のイメージバージョンを保持します