docker in incus with cuda

容器使用 CUDA

首先是 nvidia-container-toolkit 需要安装。然后 incus launch 时需要加上一些 flag。 常规的包括:

  1. -c security.nesting=true
  2. -c security.syscalls.intercept.mknod=true
  3. -c security.syscalls.intercept.setxattr=true

这几个都是在 incus 里面运行 docker 所需要的,常规操作没有太多变化。而为了 CUDA,还需要以下flag:

  1. -c nvidia.runtime=true,这样就会自动挂载一些 binary 和 library 进去,从而不需要在里面浪费空间再装一次。但是这里有坑,详见 capabilities 配置
  2. -c nvidia.driver.capabilities=compute,utility,video,特别是最后的 video。默认情况下只会设置 compute 和 utility,导致容器内找不到 encoder 的库,用不了硬件加速 codec。

为了最大兼容性,毕竟 ubuntu 等系统的驱动版本可能不一致,导致其他应用不兼容,所以容器的系统我用了跟 host 一样的 archlinux,所以完整的 launch 是:

1
2
3
4
5
6
incus launch images:archlinux/cloud/amd64 dockerd \
-c security.nesting=true \
-c security.syscalls.intercept.mknod=true \
-c security.syscalls.intercept.setxattr=true \
-c nvidia.runtime=true \
-c nvidia.driver.capabilities=compute,utility,video

除了这些flag,还需要把 gpu 添加给容器[1]。使用 physical type 也没有关系,不会导致 container 独占GPU的:

incus config device add dockerd nvidia0 gpu

此时在容器内安装 ffmpeg 并用以下命令测试[2],应该不会提示任何错误的:

1
ffmpeg -loglevel error -f lavfi -i color=black:s=1080x1080 -vframes 1 -an -c:v hevc_nvenc -f null -

挂载 GPU PCI Bus 目录到容器

如果在容器启动前就挂载,启动之后由于 host 的 nvidia-container-toolkit 会挂载 /proc/driver/nvidia,导致 gpus 目录被隐藏掉。所以需要在容器启动后,docker.service 启动前再挂载。

我们可以用一个 systemd service unit 来实现这个效果,参考的是 How to manage vms/containers with incus

首先要确定显卡的 PCIe ID,执行nvidia-smiBus-Id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Fri Oct 31 04:16:21 2025       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.95.05 Driver Version: 580.95.05 CUDA Version: 13.0 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA L4 Off | 00000000:01:00.0 Off | 0 |
| N/A 40C P8 16W / 72W | 0MiB / 23034MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+

只需要新建 /etc/systemd/system/fix-gpu-passthrough.service,注意 ID 开头只有4个0,而不是8个0:

1
2
3
4
5
6
7
8
9
10
11
12
13
#/etc/systemd/system/fix-gpu-passthrough.service 
[Unit]
Description=Creates Symlink required for LXC/Nvidia
Before=docker.service

[Service]
User=root
Group=root
ExecStart=/bin/bash -c 'mkdir -p /proc/driver/nvidia/gpus && ln -s /dev/gpu /proc/driver/nvidia/gpus/0000:01:00.0'
Type=oneshot

[Install]
WantedBy=multi-user.target

enable 并 start 就可以了。

docker 配置

容器内依然需要安装 nvidia-container-toolkit,而且需要修改配置禁用 cgroup,否则会报错:

nvidia-container-cli: mount error: failed to add device rules: unable to find any existing device filters attached to the cgroup: bpf_prog_query(BPF_CGROUP_DEVICE) failed: operation not permitted

修改 /etc/nvidia-container-runtime/config.toml 文件,忽略其他内容,把 no-cgroups 设置为 true 即可:

1
2
3
[nvidia-container-cli]
# no-cgroups = false
no-cgroups = true

测试

用 nvidia 的 cuda 容器测试一下:

1
docker run --rm --gpus all nvidia/cuda:12.8.1-cudnn-runtime-ubuntu24.04 nvidia-smi

能够正确输出 GPU 信息,就成功了。

其他有用的信息

Capabilities

参考nvidia的文档:driver capabilities。简单来说:

  • compute: CUDA
  • utility: 使用 nvidia-smi 需要这个
  • video: 视频编解码

其他的通常无关紧要。为什么需要手动指定这三个而不 all,因为 all 会直接让 incus start 报错,启动失败。


  1. Enabling GPU passthrough post launch? - simon’s answer ↩︎

  2. test if NVIDIA is available? - nmkd’s answer ↩︎