2746 字
14 分钟
【ROS】ROS+Docker:分离工作系统与ROS版本紧耦合
2026-05-24

背景#

在做Robotics相关工作的学习时,常常需要复现一些基于ROS系统开发的算法工作。为了让复现的结果尽可能贴近原工作呈现的效果,以及不想碰到一些奇奇怪怪的编译、环境问题,我总是去安装特定的Ubuntu系统,然后在安装好的系统上安装配置ROS环境。前些年这样的工作也许就只是在ros noetic与ros humble中进行切换,我也就在我的工作电脑上安装了Ubuntu20.04与Ubuntu22.04两个环境。

但随着Claude Code CLI, Codex, Opencode等AI Agent的发展,不使用这种AI工作流进行科研学习已经很难做到。这时候,如果你尝试在Ubuntu20.04上尝试安装一些AI工具,会发现因为过老的软件库版本,很多工具已经不能直接使用apt进行安装。也许你可以通过一些特殊的技巧,自己解决系统中的环境依赖问题,但这也会导致当前进行工作的系统变得复杂,时间久了难以维护。

当然,还有一个原因是我作为一个dotfile爱好者,在两个系统中配置各种工具,实在是一件很蠢的事情,并且不同的系统之间很多时候不能使用同一套配置,你得根据系统的不同去修改你的配置文件。除此之外一些工具也停止对旧的系统进行支持,例如在Ubuntu20.04上,你会因为libwebkit2gtklibjavascriptcoregtk而无法直接通过apt安装Clash Verge。对我来说还有配NeoVim和Fcitx5的时候遇到的系统gcc版本的问题

既然Ubuntu也无法提供学习时开箱即用的便利,那就决定干脆一点,直接放弃使用Ubuntu。使用Fedora作为我的工作系统,这样不仅能享受到更新的内核更新,享受到各种DE新的特性,还能满足我的折腾需求,何乐而不为呢。至于ROS项目嘛,就交给Docker了。

Docker安装#

记录一下在Fedora上安装Docker的过程Docker 官方文档

Docker推荐直接安装Docker Desktop以获得完整的支持,但据我目前的使用感受来说,不管是在Ubuntu上,还是在Windows上,Docker Desktop都挺鸡肋的,平时基本打不开,管理镜像的时候不如命令行方便,加上这玩意儿的GUI与使用DE有一定关系,索性就只安装一些满足需要的包。

Terminal window
sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

对应包的功能:

工具作用
Docker Engine提供容器运行和管理所需的核心技术,是Docker的“心脏”。
Docker Desktop包含Docker Engine、CLI等组件的一体化桌面应用程序,旨在降低使用门槛。
Docker CLI与Docker Engine交互的命令行工具,通过指令管理容器、镜像等。
Docker Build用于将应用和依赖打包成标准化、可移植容器镜像的工具和生态系统。
Docker Compose用于定义和运行多容器应用的工具,通过一个命令就能启动整个服务集群。

将当前用户添加到Docker用户组,以便不使用sudo运行docker命令:

Terminal window
# 创建docker组(通常安装时已自动创建)
sudo groupadd docker
# 将当前用户加入docker组
sudo usermod -aG docker $USER
# 执行以下命令使组权限生效
newgrp docker

启动服务:

Terminal window
sudo systemctl start docker
TIP

这里使用start而不是各类教程里面提到的enable --now是因为我是在笔记本上进行部署的,我不希望在没有运行镜像需求的时候后台运行docker服务,以节省系统开销

安装验证:

Terminal window
sudo docker run hello-world

制作项目镜像#

ROS项目复现#

拉取镜像#

要使用的镜像都可以在Docker Hub上找到(没有魔法可以尝试加速站,如轩辕加速站免费版

ROS项目复现只需要Docker提供一个集成了对应ROS版本的Ubuntu系统环境即可 ,这里可以直接用ros官方镜像(以ros noetic为例):

Terminal window
# 一般镜像命名ros:<ros版本>-ros-base
docker pull ros:noetic-ros-base

配置GUI映射#

这部分主要用于解决ROS的一些GUI工具,如Rviz、Gazebo之类的图形化界面的显示问题。

TIP

ROS1中的工具链不支持Wayland的通信协议,因此在Wayland下只能借助XWayland进行X11转发。

  1. 确保XWayland正常运行:
Terminal window
ls /tmp/.X11-unix
  1. 安装xhost工具配置XWayland访问权限:
Terminal window
# 安装 xhost
sudo dnf install xhost
# 允许所有本地连接访问 X 服务器
xhost +local:
# 在完成工作后取消访问权限
xhost -local:
  1. Docker显示配置:
Terminal window
# 将宿主机的显示器地址(如 `:0`)传给容器。
-e DISPLAY=$DISPLAY

进入容器后,初始化 ROS 环境并启动 Rviz。为强制使用 X11 后端,需设置环境变量 QT_QPA_PLATFORM=xcb

Terminal window
# 在容器内执行
source /opt/ros/noetic/setup.bash
export QT_QPA_PLATFORM=xcb
rviz

在容器中启动镜像#

Terminal window
docker run -it --rm \
--name my_ros_container \
--network host \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
osrf/ros:noetic-desktop-full \
/bin/bash

在 Docker 中运行 ROS 项目的 docker run 命令的关键参数:

-it#

  • -i (interactive)保持 STDIN 打开,即使没有附加终端。这让容器能够持续接收用户输入。
  • -t (tty):为容器分配一个伪终端,能看到的终端具备完整的交互能力(如显示颜色、支持 Ctrl+C 等)。

--rm#

容器退出后自动删除容器及其相关的匿名卷。

  • 不设置时,容器停止后会保留在本地,占据存储空间,需手动执行 docker rm 清理。
  • 适合临时、一次性任务,让宿主机环境保持干净。
  • 注意:删除后,容器内的所有改动(除非写入挂载的数据卷)都会丢失。

在复现过程中不建议加上这个选项,因为这会删除你辛辛苦苦配置的环境 ::

--name my_ros_container#

为容器指定一个自定义名称

  • 不指定时,Docker 会自动生成一个随机名字(如 boring_lamport)。
  • 有了名字后,可以直接用 docker exec -it my_ros_container bash 进入正在运行的容器,或用于停止、删除等操作,比用容器 ID 更方便。

--network host#

使用宿主机网络模式,容器与宿主机共享同一个网络栈。

  • 容器内 localhost 直接指向宿主机的回环接口。
  • 在 ROS 场景下,这非常重要:Rviz 需要连接宿主机上运行的 roscore,或者多个 ROS 节点之间通过标准端口通信时,host 网络模式能避免复杂的端口映射和网络转发配置。
  • 代价:网络隔离性降低,容器可以直接访问宿主机的所有网络接口。
TIP

还有一个隐藏的好处,就是容器可以直接走在宿主机设置的网络代理

-e DISPLAY=$DISPLAY#

设置容器内的环境变量

  • DISPLAY 告诉 X11 客户端应该连接到哪个显示服务器。$DISPLAY 是宿主机上当前会话的显示器地址,通常为 :0
  • 传入该变量后,容器内的 Rviz 等 GUI 程序才知道将窗口渲染到宿主机屏幕上。

-v /tmp/.X11-unix:/tmp/.X11-unix#

挂载一个数据卷(Volume),格式为 宿主机路径:容器内路径

  • /tmp/.X11-unix 目录下存放着 X11 服务器的 Unix 域套接字文件,是 X11 通信的通道。
  • 挂载后,容器内的应用程序可以通过这个通道与宿主机的 X 服务器(或 XWayland)通信,从而显示图形窗口。
  • 使用这个命令还可以直接挂载整个项目:
Terminal window
-v /workspace:/ros_ws

ros:noetic-ros-base#

指定使用的 Docker 镜像

最后的 /bin/bashcommand#

覆盖镜像默认的启动命令。

  • 如果提供 /bin/bash,则容器启动后直接进入一个交互式 shell,可以手动执行后续 ROS 命令。
  • 如果希望容器启动后自动运行某个程序,可以直接写,例如 /bin/bash -c "source /opt/ros/noetic/setup.bash && rviz"

其他配置#

  • -d后台运行容器,不占用终端(适合长期运行的服务,但需与其他命令如 exec 配合交互)。
  • -p 8080:80端口映射,将容器内 80 端口映射到宿主机 8080 端口(仅在 bridge 网络模式下有效,host 模式下无效)。
  • --gpus all启用 GPU 支持,需安装 NVIDIA Container Toolkit。
  • --device /dev/dri挂载宿主机 DRM 设备,用于显卡硬件加速。
  • --user $(id -u):$(id -g):以当前用户的 UID 和 GID 运行容器,避免权限问题。

ROS 开发#

除了复现之外,可能选定了某个idea,想要在现有的工作上进行开发,这时候虽然也能直接在基础的镜像上进行开发,但为了让环境可移植(和其他人协同工作,或者单纯想在自己电脑和实验室电脑中的环境保持一致)。这时候可以将上述环境打包成DockerFile, 别人想用的时候直接构建镜像就好了。

镜像制作#

以我现在正在做的Avoid MPC为例,可以编写如下的一个DockerFile:

# 拉取镜像
FROM osrf/ros:noetic-ros-base
# 使用 bash 以便 source
SHELL ["/bin/bash", "-c"]
# --- 1. 安装系统工具和 ROS 包依赖 ---
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
# 通用工具
python3-catkin-tools \
python3-pip \
git \
wget \
unzip \
# 线性代数、优化、点云等库
libmetis-dev \
gfortran \
libeigen3-dev \
libpcl-dev \
# 项目所需的 ROS 包
ros-noetic-mavros \
ros-noetic-mavros-extras \
ros-noetic-pcl-conversions \
ros-noetic-tf2-sensor-msgs \
&& rm -rf /var/lib/apt/lists/*
# --- 2. 从源码编译 CasADi 并安装到 /opt/casadi ---
WORKDIR /tmp/casadi_build
RUN git clone https://github.com/casadi/casadi.git -b 3.6.4 && \
mkdir -p casadi/build && cd casadi/build && \
cmake .. \
-DWITH_BUILD_IPOPT=ON \
-DWITH_BUILD_MUMPS=ON \
-DWITH_IPOPT=ON \
-DWITH_MUMPS=ON \
-DWITH_OPENMP=ON \
-DCMAKE_INSTALL_PREFIX=/opt/casadi && \
make -j$(nproc) && \
make install && \
# 清理临时文件
rm -rf /tmp/casadi_build
# 安装 Python 版 CasADi(用于生成 MPC 库)
RUN pip3 install casadi
# --- 3. 设置环境变量(每次启动容器时自动生效)---
# 将 CasADi 路径写入系统环境,ROS 工作空间稍后在运行时 source
RUN echo 'export LD_LIBRARY_PATH=/opt/casadi/lib:$LD_LIBRARY_PATH' >> /etc/bash.bashrc && \
echo 'export CMAKE_PREFIX_PATH=/opt/casadi:$CMAKE_PREFIX_PATH' >> /etc/bash.bashrc && \
echo 'source /opt/ros/noetic/setup.bash' >> /etc/bash.bashrc
# 为方便,创建一个工作目录(不是 ROS 工作空间,只是挂载点)
WORKDIR /ros_ws

使用该DockerFile构建镜像:

Terminal window
docker build --network host -t avoid_mpc_env:noetic .
NOTE

这里使用--network标签是因为构建过程中用到了git拉取远程仓库,我需要走我宿主机上的代理,才能顺利访问github

镜像使用#

与上文提到的ROS项目复现一致,这时候镜像中已经包含了该项目需要的所有环境,想要运行直接在容器中启动这个容器即可。当然你也可以在项目中添加这么一个脚本,更优雅地启动:

run_docker.sh
#!/bin/bash
# xhost
xhost +local: > /dev/null 2>&1
# run docker (这时候可以加上--rm标签了)
docker run -it --rm --name avoid_mpc_dev \
--network host \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v "$HOME/workspace/Avoid-MPC:/ros_ws" \
avoid_mpc_dev:noetic \
/bin/bash
# xhost off
# xhost -local: > /dev/null 2>&1
【ROS】ROS+Docker:分离工作系统与ROS版本紧耦合
http://onemom.top/posts/ros-docker/
作者
onemotre
发布于
2026-05-24
许可协议
CC BY-NC-SA 4.0