Docker使用教程
前言
Docker 是日常开发里很常用的容器工具,可以把程序和运行环境打包在一起,减少“换台机器就跑不起来”的问题。
这篇文章先整理 Docker 的基本概念和常用命令,再给出一个 ROS Noetic 图形化容器的实际示例,方便后续继续搭建自己的仿真环境。
一、先理解镜像和容器
可以先把两者理解成下面这个关系:
1 | 镜像 -> 创建 -> 容器 |
- 镜像(Image):一个静态模板,里面包含程序代码、依赖库和运行环境。
- 容器(Container):由镜像创建出来的运行实例,可以直接启动和使用。
例如,下面这条命令会基于 ubuntu 镜像启动一个 Ubuntu 容器,并进入容器终端:
1 | docker run -it ubuntu bash |
一个镜像可以创建多个容器,而多个容器之间彼此独立。
二、常用命令速查
| 功能 | 命令 |
|---|---|
| 查看 Docker 状态 | docker info |
| 查看本地镜像 | docker images |
| 拉取镜像 | docker pull <镜像名> |
| 查看正在运行的容器 | docker ps |
| 查看所有容器 | docker ps -a |
| 查看容器日志 | docker logs <容器名或容器ID> |
| 停止容器 | docker stop <容器名或容器ID> |
| 启动并进入容器 | docker start -ai <容器名或容器ID> |
| 进入正在运行的容器 | docker exec -it <容器名或容器ID> bash |
| 删除容器 | docker rm <容器名或容器ID> |
| 删除镜像 | docker rmi <镜像名或镜像ID> |
下面把这些命令按使用场景简单展开一下。
1. 查看 Docker 是否正常工作
1 | docker info |
这个命令可以查看 Docker 的整体运行状态,比如版本、容器数量、镜像数量、存储目录和系统信息。
如果执行时提示无法连接 Docker daemon,可以先检查服务状态:
1 | sudo systemctl status docker |
如果 Docker 服务没有启动,再执行:
1 | sudo systemctl start docker |
2. 查看本地镜像
1 | docker images |
用于查看本机已经下载或构建好的镜像。
3. 拉取镜像
1 | docker pull osrf/ros:noetic-desktop-full |
这条命令会下载 ROS Noetic 的桌面完整版镜像,后面运行 ROS 图形程序时会直接用到。
如果是在 Jetson 这类 ARM64 设备上,只需要 ROS Noetic 的基础运行环境,可以拉取更轻量的 ros-base 镜像:
1 | docker pull --platform linux/arm64/v8 ros:noetic-ros-base-focal |
在 Jetson 本机上执行时,Docker 通常会自动选择 ARM64 镜像;这里加上 --platform linux/arm64/v8 是为了把架构写得更明确。需要注意的是,ros-base 不包含 RViz、Gazebo 这类桌面图形工具,后面如果要运行图形程序,仍然需要安装对应软件包或使用桌面版镜像。
4. 查看容器
查看正在运行的容器:
1 | docker ps |
查看所有容器(包括已经停止的容器):
1 | docker ps -a |
如果容器状态是 Exited,通常说明容器已经退出,这时可以结合日志排查:
1 | docker logs <容器名或容器ID> |
5. 停止、重启和进入容器
停止容器:
1 | docker stop <容器名或容器ID> |
容器停止后,重新启动并直接进入:
1 | docker start -ai <容器名或容器ID> |
如果容器已经在运行,想在新的终端里再进入一次:
1 | docker exec -it <容器名或容器ID> bash |
6. 删除容器和镜像
删除已经停止的容器:
1 | docker rm <容器名或容器ID> |
删除本地镜像:
1 | docker rmi <镜像名或镜像ID> |
如果镜像仍被某个容器占用,需要先删除对应容器。
三、创建一个 ROS Noetic 图形化容器
这一节给一个比较常见的使用方式:在 Docker 里运行 ROS Noetic,同时把宿主机的图形界面和整个 /root 目录挂载进去。
1. 允许容器访问宿主机图形界面
1 | xhost +SI:localuser:root |
这一步的作用是允许本机 root 用户访问当前图形显示服务,容器里的 RViz、rqt、Gazebo 等图形程序才能在宿主机屏幕上显示出来。
相比 xhost +local:root,这里更推荐 xhost +SI:localuser:root,因为它只放行本机的 root 用户,范围更收敛一些。很多 ROS / Gazebo 镜像默认就是以 root 身份运行,所以通常够用。
2. 创建并进入容器
在执行 docker run 之前,建议先在宿主机上创建挂载目录:
1 | mkdir -p ~/docker/ros_root |
这样可以避免路径写错时被 Docker 悄悄新建成空目录,也更方便你确认权限和目录位置。
1 | docker run -it \ |
这条命令会创建并进入一个名为 ros_noetic 的容器。几个关键参数的作用如下:
--network host:共享宿主机网络,ROS 通信更省事。--ipc host:共享 IPC,某些图形和仿真程序更稳定。--gpus all:把 GPU 暴露给容器;如果机器没有 NVIDIA 环境,可以先去掉这一项。-e DISPLAY=$DISPLAY和-v /tmp/.X11-unix:/tmp/.X11-unix:让容器里的图形程序能显示到宿主机。-e XAUTHORITY=/root/.Xauthority和-v $HOME/.Xauthority:/root/.Xauthority:ro:把宿主机当前图形会话的 X11 认证信息挂进容器,很多情况下可以减少手动执行xhost的需要。-v ~/docker/ros_root:/root:把容器内整个/root目录挂载到宿主机,代码、配置和 home 目录下的其他文件都会一起保存。
如果后续需要让容器里的程序访问宿主机串口或 USB 设备,例如 /dev/ttyACM0、/dev/ttyUSB0、/dev/ttyTHS1,通常要在创建容器时就加上 --device 参数:
1 | docker run -it \ |
这里有几个需要注意的点:
--network host和目录挂载并不会自动让容器获得串口访问权限,设备需要单独通过--device暴露进去。--device=/dev/ttyACM0这种写法要求宿主机上当前确实存在这个设备,否则容器创建时会报错。- 如果你创建容器时没有指定
--device,后续不能通过docker start或docker exec再补上,一般需要删除旧容器后重新创建。 - 如果只是不确定设备号,可以先在宿主机执行
ls /dev/ttyUSB* /dev/ttyACM* /dev/ttyTHS*确认实际存在的设备,再决定要不要加到启动命令里。
退出容器时直接执行:
1 | exit |
3. 后续重新进入容器
容器停止后,重新启动并进入:
1 | docker start -ai ros_noetic |
如果容器已经在运行,另开一个终端进入:
1 | docker exec -it ros_noetic bash |
四、在容器里编译 Diff-Planner
下面以 Diff-Planner 为例,演示如何在容器里编译一个 ROS1 工程。
依赖安装
apt update
apt-get install git
1. 进入源码目录并下载项目
1 | mkdir -p /root/catkin_ws/src |
2. 安装依赖并编译
Diff-Planner 项目本身就是一个 catkin 工作空间,因此这里直接进入项目目录编译:
1 | cd /root/catkin_ws/src/Diff-Planner |
3. 启动测试
1 | cd /root/catkin_ws/src/Diff-Planner |
如果还需要在另一个终端里触发脚本,可以新开一个终端执行:
1 | docker exec -it ros_noetic bash |
五、挂载 /root 的作用
前面这条挂载:
1 | -v ~/docker/ros_root:/root |
表示宿主机目录 ~/docker/ros_root 和容器目录 /root 是同步的。
也就是说:
- 你在宿主机里修改
~/docker/ros_root的内容,容器里会同步变化。 - 你在容器里放在
/root下的代码、配置、日志和脚本,宿主机里也能直接看到。
这样即使容器删掉了,/root 目录下的数据依然保存在宿主机,不容易丢。
不过要注意,挂载 /root 只能保存用户目录里的内容,不能代替整个容器系统本身。比如通过 apt install 安装到 /usr、/etc、/var 下的系统级依赖,仍然属于容器环境的一部分。
六、如何保存容器环境
前面挂载 /root 可以保存代码、配置和日志,但如果你在容器里额外安装了软件包、改了系统环境,单纯保留 /root 还不够。这时候通常有两种方式保存容器环境。
1. 用 docker commit 保存当前容器
如果你已经在容器里手动装好了依赖,想先把当前状态直接保存下来,可以执行:
1 | docker commit ros_noetic ros_noetic_saved:latest |
这条命令会把当前容器 ros_noetic 保存成一个新的镜像 ros_noetic_saved:latest。
这里要注意:docker commit 保存的是容器当前文件系统状态,但像 -v ~/docker/ros_root:/root 这种挂载到宿主机的目录,本来就不属于镜像本体,所以不会被重新打包进镜像里。不过这部分数据已经在宿主机上,一般也不需要靠 commit 再保存一次。
之后就可以基于这个镜像重新创建容器:
1 | docker run -it \ |
如果你之后需要补 --device、修改挂载目录,或者调整其他启动参数,比较常见的做法就是:
1 | docker commit ros_noetic ros_noetic_saved:latest |
然后再用新的 docker run 命令重新创建。
docker commit 的优点是快,适合先把当前能跑通的环境存下来;缺点是步骤不可追踪,后面时间长了不容易回忆当初到底改过什么。
2. 用 Dockerfile 固化环境
如果这个环境后续要长期维护,或者以后要迁移到别的机器,更推荐把安装步骤写成 Dockerfile。
例如:
1 | FROM osrf/ros:noetic-desktop-full |
然后执行:
1 | docker build -t ros_noetic_custom:latest . |
以后直接基于这个自定义镜像启动容器即可:
1 | docker run -it \ |
Dockerfile 的优点是可复现、可维护、方便迁移;缺点是第一次整理会比 docker commit 麻烦一些。
3. 该怎么选
- 只是临时保存当前能跑通的环境:优先用
docker commit - 想长期维护,或者准备换机器复现:更推荐写
Dockerfile - 比较实用的做法是:先
docker commit兜底,再有空把环境整理成Dockerfile
如果后面还想把镜像拷到别的机器,也可以再配合使用:
1 | docker save -o ros_noetic_saved.tar ros_noetic_saved:latest |
七、常见问题
1. 无法连接 Docker daemon
先检查 Docker 服务:
1 | sudo systemctl status docker |
如未启动:
1 | sudo systemctl start docker |
2. Gazebo 启动时报图形权限错误
如果宿主机重启后,在容器里运行 gazebo 出现下面这个报错:
1 | Authorization required, but no authorization protocol specified |
说明容器当前没有权限连接宿主机显示器。重新执行一次:
1 | xhost +SI:localuser:root |
通常在宿主机重启、注销重登,或者 X / 图形会话重启后,都需要再执行一遍。因为 xhost 授权是给当前图形会话的,不是永久系统配置。
如果你希望每次登录图形界面后自动执行,可以把下面这行加入宿主机的登录启动脚本,例如 ~/.profile,或者桌面环境的“启动应用程序”里:
1 | xhost +SI:localuser:root |
这样做的效果更接近“半永久配置”:不是系统级永久放行,而是每次图形会话启动后自动重新授权。
如果想再干净一点,可以在创建容器时正确挂载 X11 认证文件:
1 | -e DISPLAY=$DISPLAY \ |
这种方式会把宿主机当前用户的 .Xauthority 挂到容器内,很多情况下即使不手动执行 xhost,容器里的 Gazebo、RViz 也能直接连接显示服务。
不过在 ROS / Gazebo 的 Docker 环境里,最省事的做法通常还是:宿主机每次开机并进入桌面后,先执行一次:
1 | xhost +SI:localuser:root |
再进入容器启动 Gazebo 或 RViz。
3. 没有 NVIDIA 显卡怎么办
如果你的机器没有配置 NVIDIA 驱动或 Docker GPU 环境,创建容器时先去掉:
1 | --gpus all |
以及:
1 | -e NVIDIA_DRIVER_CAPABILITIES=all |
总结
Docker 的核心思路并不复杂:先有镜像,再由镜像创建容器。真正高频使用的内容,其实就是拉镜像、启容器、进容器、挂目录和排查图形权限这几件事。
把这些基础操作熟悉之后,再继续搭 ROS、Gazebo、PX4 之类的开发环境会顺很多。