Skip to main content

ROS 2 Docker Guide 2026

Docker is the standard way to ship reproducible ROS 2 environments — isolating dependencies, enabling CI, and simplifying deployment on robots. This guide covers official images, multi-stage builds, GPU passthrough, docker compose, and VS Code devcontainers.

Official ROS 2 Docker Images

Image tagContentsUse case
ros:jazzyros-base (minimal)Base for custom builds
ros:jazzy-ros-baseros-base + rosdepBuild environment
ros:jazzy-ros-coreroscpp rclpy onlySmallest runtime
ros:jazzy-desktopros-base + rviz2 + rqtDev/demo workstation
ros:jazzy-desktop-fulldesktop + sim packagesFull simulation
osrf/ros:jazzy-desktop-nvidiadesktop + CUDA + cuDNNIsaac ROS / GPU SLAM
# Quick start — run a ROS 2 shell
docker run -it --rm ros:jazzy bash

# Source and check
source /opt/ros/jazzy/setup.bash
ros2 topic list

Multi-Stage Dockerfile Pattern

# syntax=docker/dockerfile:1
ARG ROS_DISTRO=jazzy

# ── Stage 1: dependency cache ──────────────────────────────
FROM ros:{ROS_DISTRO{"}"}-ros-base AS deps
WORKDIR /ros2_ws

# Copy only package manifests to cache rosdep installs
COPY src/my_robot/package.xml src/my_robot/package.xml
COPY src/my_msgs/package.xml  src/my_msgs/package.xml

RUN apt-get update && rosdep update && \
    rosdep install --from-paths src --ignore-src -y && \
    rm -rf /var/lib/apt/lists/*

# ── Stage 2: build ─────────────────────────────────────────
FROM deps AS builder
COPY src/ src/
RUN . /opt/ros/{ROS_DISTRO{"}"}/setup.sh && \
    colcon build --symlink-install \
      --cmake-args -DCMAKE_BUILD_TYPE=Release

# ── Stage 3: runtime (smallest possible) ──────────────────
FROM ros:{ROS_DISTRO{"}"}-ros-core AS runtime
WORKDIR /ros2_ws

# Copy only install artifacts, not build/
COPY --from=builder /ros2_ws/install ./install

COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["ros2", "launch", "my_robot", "bringup.launch.py"]

entrypoint.sh

#!/bin/bash
set -e
source /opt/ros/jazzy/setup.bash
source /ros2_ws/install/setup.bash
exec "$@"

Build & Run

# Build all stages
docker build --target runtime -t my_robot:latest .

# Run with host network (ROS 2 discovery uses multicast)
docker run -it --rm --network host my_robot:latest

# Connect a second ROS 2 terminal to the same network
docker run -it --rm --network host ros:jazzy bash
# inside: source /opt/ros/jazzy/setup.bash && ros2 node list

# Pass environment variables
docker run -it --rm \
  --network host \
  -e ROS_DOMAIN_ID=42 \
  my_robot:latest

GPU Passthrough (Isaac ROS / CUDA)

# Prerequisite: nvidia-container-toolkit installed on host
# https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html

# Run with full GPU access
docker run -it --rm \
  --gpus all \
  --network host \
  nvcr.io/nvidia/isaac/ros:jazzy-isaac_ros_base_aarch64 \
  bash

# Verify CUDA inside container
nvidia-smi
python3 -c "import torch; print(torch.cuda.is_available())"

# X11 display forwarding (for GUI tools on Linux host)
docker run -it --rm \
  --gpus all \
  --network host \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  osrf/ros:jazzy-desktop-nvidia \
  rviz2

docker compose — Full Simulation Stack

# compose.yaml
services:
  gazebo:
    image: osrf/ros:jazzy-desktop-full
    network_mode: host
    environment:
      - DISPLAY={DISPLAY{"}"}
      - ROS_DOMAIN_ID=0
    volumes:
      - /tmp/.X11-unix:/tmp/.X11-unix
    command: >
      bash -c "source /opt/ros/jazzy/setup.bash &&
               gz sim -r worlds/empty.sdf"

  robot_bringup:
    build:
      context: .
      target: runtime
    network_mode: host
    environment:
      - ROS_DOMAIN_ID=0
    depends_on:
      - gazebo
    command: ros2 launch my_robot sim.launch.py

  nav2:
    image: ros:jazzy-ros-base
    network_mode: host
    environment:
      - ROS_DOMAIN_ID=0
    depends_on:
      - robot_bringup
    command: >
      bash -c "source /opt/ros/jazzy/setup.bash &&
               ros2 launch nav2_bringup bringup_launch.py
               use_sim_time:=True
               params_file:=/config/nav2_params.yaml"
    volumes:
      - ./config:/config
# Start stack
docker compose up

# Rebuild after Dockerfile change
docker compose up --build

# Teardown
docker compose down

VS Code Dev Container

// .devcontainer/devcontainer.json
{
  "name": "ROS 2 Jazzy",
  "image": "ros:jazzy-desktop",
  "runArgs": ["--network=host", "--privileged"],
  "containerEnv": {
    "ROS_DOMAIN_ID": "0",
    "DISPLAY": "{localEnv:DISPLAY{"}"}",
    "RCUTILS_COLORIZED_OUTPUT": "1"
  },
  "mounts": [
    "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind"
  ],
  "postCreateCommand": "sudo apt-get update && rosdep update && rosdep install --from-paths src --ignore-src -y",
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-vscode.cpptools",
        "ms-python.python",
        "smilerobotics.urdf",
        "nonanika.ros",
        "ms-iot.vscode-ros"
      ]
    }
  }
}

rosdep in Containers

# Initialize rosdep (first time per container layer)
rosdep init || true      # silently ignore if already initialized
rosdep update

# Install deps from workspace
cd /ros2_ws
rosdep install \
  --from-paths src \
  --ignore-src \
  --rosdistro jazzy \
  -y

# In CI (no interactive prompts)
DEBIAN_FRONTEND=noninteractive rosdep install \
  --from-paths src --ignore-src -y

Common Issues

ROS 2 nodes can't discover each other across containers

Use --network host (not bridge). ROS 2 discovery (DDS multicast) requires L2 broadcast — bridge networks block it.

rviz2 / GUI won't open in container

Pass -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix. Run xhost +local:docker on host first.

CUDA not available inside container

nvidia-container-toolkit not installed on host, or --gpus all not passed. Run nvidia-smi on host to verify driver.

rosdep init fails: 'oops in update'

Behind a corporate proxy — set http_proxy/https_proxy env vars, or use ROSDEP_SOURCES_YAML to point to a local mirror.

Layer cache invalidated on every build

COPY src/ too early. Copy package.xml files first (deps stage), run rosdep, THEN COPY src/ in builder stage.

Next Steps