最近不少人在 Ubuntu 24.04 上部署开发环境时,会遇到一个很实际的顺序问题:硬件平台装好了驱动,比如 170hx 这类嵌入式视觉主机的串口、USB 转串口芯片、调试器或者 GPU 相关驱动,接下来想装 Docker 跑服务,结果发现 Docker 要么装不上,要么装上了容器里却看不到设备、拉镜像卡到怀疑人生。
如果你也卡在这个阶段,这篇文章适合你。
先说结论:在 Ubuntu 24.04 上安装 Docker 本身是一件很标准的事情,难度不高;真正容易踩坑的,是“系统已经装好驱动之后”这个状态。驱动会抢占内核模块、修改 udev 规则,Docker 则依赖容器运行时与 systemd 服务管理,这两套逻辑如果没理顺,你会看到各种权限错误、设备不可见和网络超时。
读完本文,你能掌握四件事:在 Ubuntu 24.04 上正确安装 Docker Engine;理解驱动与容器运行时的关系;学会把串口、调试器等设备映射进容器并验证;以及一套常见的排错思路,避免反复重装系统。
1. 为什么驱动装好后再装 Docker 更容易踩坑
很多教程默认你的系统是一台“干净”的 Ubuntu,但现实不是这样。尤其是嵌入式、视觉开发和硬件调试相关的机器,驱动安装往往是第一步:
- 安装 USB 转串口芯片驱动,比如 CH340、CP2102、FT232;
- 安装调试器驱动,比如 ST-Link、J-Link;
- 安装视觉模块相关驱动,比如摄像头、图像采集卡;
- 安装 GPU 驱动,比如 NVIDIA 系列,用于视觉推理或渲染。
这些驱动在系统里做了不少“底层动作”:加载内核模块、注册 udev 规则、修改设备节点权限。表面上看它们和 Docker 没有直接关联,但 Docker 容器的运行依赖/dev设备节点、/var/run/docker.sock通信套接字、网络命名空间和 cgroup 权限体系。一旦设备的属主、权限和套接字权限不一致,就会出现“Docker 装好了,但容器跑起来什么都用不了”的尴尬局面。
另一个问题是依赖冲突。Ubuntu 24.04 自带的软件源里可能已经有docker.io包,它和 Docker 官方源提供的docker-ce包是两套不同的构建体系。如果你在装驱动时已经顺手装了docker.io,后续再安装官方 Docker 就会遇到包冲突。很多人在这一步直接卡死。
所以这里真正要注意的不是哪条安装命令没用熟,而是要先理解:驱动负责“让内核认识硬件”,Docker 负责“让容器共享内核能力”。前者在后,后者在上,顺序错了,后面全乱。
2. Ubuntu 24.04 安装 Docker Engine 的前置准备
在输入安装命令之前,建议先花两分钟确认系统状态,这几个步骤能避免大多数低级问题。
2.1 确认系统版本
Ubuntu 24.04 是 LTS 版本,官方长期支持到 2029 年。先确认你的系统确实是 24.04:
lsb_release -a cat /etc/os-release如果输出中VERSION_ID="24.04",就可以继续。如果是从 22.04 升级到 24.04 的系统,建议额外留意一下内核版本和系统源是否干净:
uname -r升级过来的系统有时会残留旧内核模块,驱动加载容易出问题。
2.2 确认是否已经安装过 Docker
这一步非常关键。很多机器里其实已经装过 Docker,只是你自己忘了,或者驱动安装脚本自动拉取了依赖:
which docker dpkg -l | grep -i docker如果存在旧版本,使用官方脚本安装前,先清掉旧包:
sudo apt remove docker docker-engine docker.io containerd runc需要说明的是,/var/lib/docker这个目录下面可能还保留着旧的镜像和数据。如果你确认不需要旧数据,可以一并删除;如果里面有重要数据,建议先备份再操作。
2.3 更新软件源与安装依赖
更新系统索引,并安装后续添加 Docker 官方源所需要的工具:
sudo apt update sudo apt install -y ca-certificates curl gnupg如果你的服务器访问 Ubuntu 官方源很慢,也可以换成国内镜像源。Ubuntu 24.04 的源配置可能同时存在于/etc/apt/sources.list或/etc/apt/sources.list.d/ubuntu.sources,具体以你的系统文件为准,替换成阿里云、腾讯云等镜像站地址即可。
这里多说一句:换源不是必须步骤,但如果你在执行apt update时经常超时,优先检查网络环境和源配置,而不是直接盲目重装。
3. 使用官方 apt 仓库安装 Docker Engine
在 Ubuntu 上安装 Docker,我推荐使用 Docker 官方 apt 仓库,而不是直接执行apt install docker.io。原因是官方仓库更新及时,并且会附带containerd、buildx、compose插件,后续做镜像构建和多容器编排时不需要额外折腾。
3.1 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg这里的作用是把 Docker 官方仓库的 GPG 密钥安装到系统里,后续apt校验包时能确保包来自官方。
3.2 添加 Docker 软件源
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null执行完成后,建议检查一下写入的源文件内容,确认源地址和系统版本代号正确:
cat /etc/apt/sources.list.d/docker.list3.3 安装 Docker 核心组件
sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin安装完成后,启动 Docker 并设置开机自启:
sudo systemctl enable --now docker sudo systemctl status docker如果看到active (running),说明服务已经正常运行。
3.4 验证 Docker 安装
docker --version docker compose version docker infodocker info会输出大量系统信息,包括 Docker 根目录、存储驱动、镜像加速配置等。重点看Server Version和Storage Driver是否正常。如果Client有输出,但Server报错,说明 Docker 守护进程没有正常运行,优先检查journalctl -u docker日志。
3.5 将当前用户加入 docker 组
每次执行 docker 命令都加sudo很繁琐。更常见的做法是把用户加入docker组:
sudo usermod -aG docker $USER执行后必须重新登录或重启机器,组权限才会生效。这里提醒一句:加入docker组等价于授予了用户接近 root 的权限,生产环境要谨慎使用。如果是多用户共用的服务器,建议使用专门的运维账号,而不是给所有人开通。
3.6 配置镜像加速器
如果你发现拉取镜像很慢,或者总是超时,可以在/etc/docker/daemon.json中配置镜像加速。你所在的云服务商控制台一般会提供一个专属加速地址,请填写你自己的地址,不要照抄网络上的公共地址:
{ "registry-mirrors": ["https://your-mirror-id.mirror.example.com"] }修改完成后重启 Docker:
sudo systemctl daemon-reload sudo systemctl restart docker然后可以通过docker info确认镜像加速是否生效。
到这里,Docker 本身已经安装完成。
4. 驱动与容器运行时的关系:设备映射与 GPU 加速
前面说过,驱动装好之后真正要处理的问题是“如何让容器使用宿主机已识别的硬件”。这个概念如果不理解,后面的所有操作都会很拧巴。
4.1 容器共享内核,但不共享设备列表
Docker 容器和宿主机共享同一个 Linux 内核,也就是说驱动只要在宿主机上加载了,容器理论上就能使用对应的硬件能力。但容器看不见所有设备节点。默认情况下,容器有自己的/dev文件系统,宿主机上的/dev/ttyUSB0、/dev/video0等设备不会自动出现在容器里。
要想让容器访问某个设备,需要在启动容器时显式映射设备:
docker run --device /dev/ttyUSB0:/dev/ttyUSB0把宿主机的设备节点映射进容器后,容器进程访问这个节点就相当于直接访问物理设备。
4.2 驱动安装会影响设备节点权限
你在 170hx 这类平台上安装的串口芯片驱动、调试器驱动,通常不只是让内核识别设备,还会创建/dev/ttyUSB0、/dev/stlink等节点,并设置属主和权限。如果你的驱动安装脚本修改了 udev 规则,设备节点可能只对特定用户或 USB 插拔时的默认属主可见。这种情况下,即使你把设备映射进容器,容器内的用户也可能没有权限打开它。
解决方案有两种:
- 在启动容器时使用
--user指定容器内用户,并确保该用户在容器内有设备读写权限; - 在宿主机上调整 udev 规则,允许特定组访问设备节点。
这里更推荐后者,因为容器内的用户体系是隔离的,靠容器内调整权限比较别扭。
4.3 GPU 与视觉平台的特殊情况
如果你的视觉平台需要 GPU 直通,仅仅映射/dev/nvidia0是不够的。NVIDIA 的容器方案需要额外安装nvidia-container-toolkit,让 Docker 在启动容器时自动注入 GPU 设备、驱动库和运行时工具。
大致步骤是:
# 安装 nvidia-container-toolkit(具体安装方式以官方为准) sudo apt install -y nvidia-container-toolkit # 重启 Docker 使配置生效 sudo systemctl restart docker # 启动容器时添加 --gpus 参数 docker run --rm --gpus all your-image nvidia-smi如果你没有接触过 GPU 容器,不要着急,这个属于进阶内容。先跑通基础设备映射,再逐步加 GPU 能力会更稳。对于嵌入式视觉平台,串口、摄像头、USB 调试器的设备映射反而是更常见、更优先的需求。
5. 完整示例:在 Ubuntu 24.04 中用 Docker 运行带 USB 串口设备的容器
为了让你看到一个完整闭环,我用一个最小示例演示:在容器里运行一个 Python 程序,读取宿主机上的 USB 转串口设备,并打印设备基本信息。
5.1 项目文件结构
先创建一个工作目录,并准备以下文件:
serial-demo/ ├── Dockerfile ├── serial_test.py └── requirements.txt5.2 Python 串口检测脚本
文件路径:serial_demo/serial_test.py
#!/usr/bin/env python3 import serial.tools.list_ports ports = serial.tools.list_ports.comports() if not ports: print("NO_SERIAL_PORTS_FOUND") raise SystemExit(1) for port in ports: print("DEVICE: {} - {}".format(port.device, port.description))这个脚本的作用不是复杂收发,而是检查容器里能否枚举到串口设备。如果容器里看不到设备,程序会直接提示NO_SERIAL_PORTS_FOUND,非常容易定位问题。
5.3 requirements.txt
文件路径:serial_demo/requirements.txt
pyserial==3.55.4 Dockerfile
文件路径:serial_demo/Dockerfile
FROM ubuntu:24.04 RUN apt-get update && apt-get install -y \ python3 \ python3-pip \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt COPY serial_test.py . CMD ["python3", "/app/serial_test.py"]这里有一点需要说明:容器内不需要重新安装串口芯片驱动。驱动已经加载在宿主机内核里,容器只是通过设备节点访问硬件。所以 Dockerfile 中只安装 Python 和 pyserial,不需要处理内核模块。
5.5 构建镜像
在serial_demo目录下执行:
cd serial_demo docker build -t serial-demo .构建完成后,可以用docker images确认镜像存在。
5.6 确认宿主机上的串口设备节点
容器启动前,先在宿主机上确认设备已经识别到:
lsusb ls -l /dev/ttyUSB* /dev/ttyACM*一般 USB 转串口设备会显示为/dev/ttyUSB0或者/dev/ttyACM0。如果你的设备是 ST-Link、J-Link 调试器,设备名可能是/dev/stlink或/dev/ttyACM0,以实际输出为准。如果宿主机都看不到设备,先不要启动容器,优先排查驱动。
5.7 启动容器并映射设备
假设宿主机上的串口设备是/dev/ttyUSB0:
docker run --rm --device /dev/ttyUSB0:/dev/ttyUSB0 serial-demo正常情况下会输出类似:
DEVICE: /dev/ttyUSB0 - USB Serial这个输出说明容器内部已经可以访问宿主机上的串口设备。
6. 运行结果与验证方法
设备映射类的容器问题,验证思路比代码本身更重要。建议按下面顺序逐层确认。
6.1 验证容器内设备节点
如果程序没有输出,可以先用交互模式进入容器,手动查看设备节点:
docker run -it --rm --device /dev/ttyUSB0:/dev/ttyUSB0 serial-demo /bin/bash ls -l /dev/ttyUSB0如果ls能看到设备,说明映射成功;如果看不到,说明--device参数没有生效。
6.2 验证设备可读写
除了能看到设备,还要确认容器内用户有权限访问:
docker run -it --rm --device /dev/ttyUSB0:/dev/ttyUSB0 serial-demo /bin/bash cat /dev/ttyUSB0正常情况不会报Permission denied。如果报错,说明设备节点权限有问题,返回宿主机检查 udev 规则。
6.3 查看容器配置
可以用docker inspect查看设备的 cgroup 权限配置:
docker ps docker inspect <container_id> | grep -A10 Cgroup这里能看到容器允许访问的设备列表,快速确认设备映射是否真的写入了容器配置。
6.4 查看宿主内核日志
如果设备映射没问题,但数据异常,可以回到宿主机查看内核日志:
dmesg | tail -50 journalctl -k --since "5 minutes ago"优先看 USB 设备枚举、tty 注册、驱动报错等信息。
7. 常见问题与排查思路
下面这些问题是 Ubuntu 24.04 上“先装驱动、后装 Docker”最常见的坑,建议收藏备用。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
docker: permission denied | 当前用户不在 docker 组 | 执行groups $USER | 加入 docker 组,重新登录 |
无法连接/var/run/docker.sock | Docker 守护进程未启动 | systemctl status docker | 启动并设置开机自启 |
| 拉取镜像慢或超时 | 未配置镜像加速或网络不稳定 | docker info查看 Registry Mirrors | 配置云服务商提供的加速地址 |
容器内看不到/dev/ttyUSB0 | 启动容器时未映射设备 | 查看docker inspect的设备列表 | 添加--device参数 |
| 容器内能看到设备但打不开 | 设备节点权限不足 | 在容器内执行ls -l /dev/ttyUSB0 | 调整宿主机 udev 规则或映射用户权限 |
摄像头/dev/video0不可用 | 未映射摄像头设备或驱动未加载 | 宿主机检查ls /dev/video* | 添加--device /dev/video0 |
| NVIDIA GPU 在容器内不可见 | 未安装 nvidia-container-toolkit | 在容器内运行nvidia-smi | 安装 toolkit 并添加--gpus参数 |
| Docker 服务启动失败 | 与已有系统包冲突或内核模块问题 | journalctl -u docker | 清理旧包、检查内核模块 |
| 升级内核后驱动丢失 | 驱动依赖的内核模块未自动重建 | dkms status | 重新构建 DKMS 模块 |
排查时有一个基本原则:先确认宿主机设备正常,再检查 Docker 配置,最后才查容器内环境。很多人一看到容器里没设备,就以为要在容器里重装驱动,这是一个很大的误解。
8. 最佳实践与工程建议
8.1 设备映射最小化
在启动容器时,只映射你真正需要的设备,不要图省事直接加--privileged。--privileged会让容器拥有宿主机几乎所有内核权限,一旦容器被攻破,风险非常大。
正确写法是:
docker run --device /dev/ttyUSB0:/dev/ttyUSB0 --device /dev/video0:/dev/video0 your-image这种写法每个设备都明确映射,方便审计,也便于排错。
8.2 用 Docker Compose 管理复杂设备配置
如果设备数量多,命令行的--device会变得很长,建议使用 Docker Compose。下面是一个docker-compose.yml示例:
services: vision-app: image: serial-demo devices: - "/dev/ttyUSB0:/dev/ttyUSB0" - "/dev/video0:/dev/video0" environment: - PYTHONUNBUFFERED=1 restart: unless-stopped启动方式:
docker compose up -d docker compose logs -f用 Compose 管理设备映射、环境变量和重启策略,比手敲长命令更稳,也更适合团队协作。
8.3 不要随意挂载 Docker 套接字
不建议为了在容器内管理 Docker,就轻易把/var/run/docker.sock挂载进普通业务容器。这会赋予容器操作宿主机 Docker 的能力,安全边界被打破。如果你确实需要类似能力,优先考虑专门的运维容器和最小权限方案。
8.4 慎用 latest 标签
无论是基础镜像还是业务镜像,尽量使用明确版本标签,不要随手写latest。同一个latest在不同时间拉取,可能得到完全不同的镜像内容,导致环境不可复现。更推荐使用带哈希的镜像摘要或者具体版本号,这样升级时可控。
8.5 定期清理无用镜像与日志
嵌入式开发机磁盘通常比较紧张。构建镜像时容易产生大量悬空镜像,可以定期清理:
docker system df docker system prune -f容器日志默认会不断增长,建议在/etc/docker/daemon.json中配置日志轮转:
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }这能避免日志写满磁盘。
8.6 驱动与内核升级的节奏
如果你经常升级内核,建议把涉及内核模块的驱动(如 GPU、一些 USB 芯片驱动)用 DKMS 管理。否则内核升级后,驱动模块可能没有自动重建,容器里的设备映射会全部失效。升完内核后,先确认dkms status正常,再重启系统。
9. 结语与后续方向
如果你的 170hx 平台刚装好驱动,现在想做容器化开发,第一步不要急着去拉 MySQL、Redis、Nginx 这些镜像,而是先跑通一个最小的设备映射示例。把/dev/ttyUSB0、摄像头、GPU 这些硬件资源从宿主机“传”进容器,后面部署任何服务都会顺畅很多。
Docker 安装只是起点。对于 24.04 刚上手的开发者,下一步可以重点学习三块内容:Docker Compose 多服务编排、镜像构建优化、以及容器日志和资源限制。等到你需要在多台机器上部署同样环境时,再接触镜像仓库和编排平台,会更有针对性。
这篇文章里的命令,建议收藏备用。尤其是第 7 章的排查表格,遇到设备映射问题先对照检查,能省掉大量“重装系统”的时间。