外观
在 LXC 里跑 Docker:能不能、代价是什么
这是 PVE 论坛上被问得最多的问题之一。答案不是简单的「能」或「不能」,这篇把官方立场、技术条件和实际代价摆清楚,让你自己判断值不值。
官方立场
Proxmox 官方文档对这件事的态度是明确的:推荐用虚拟机跑 Docker。较早版本的文档写得更直接——"如果你想跑应用容器(比如 Docker 镜像),推荐在一台 Proxmox QEMU 虚拟机里运行它们"。当前版本的措辞略微缓和,但结论没变:
对于需要最大隔离性、并且需要在线迁移能力的场景,把容器嵌套在一台 Proxmox QEMU 虚拟机里,仍然是推荐做法。
也就是说,官方没有说"绝对不能在 LXC 里跑 Docker",但明确把虚拟机放在推荐位置,理由是虚拟机能同时拿到"应用容器化"的好处和"强隔离、可在线迁移"这两条 LXC 本身给不了的能力。
与本站其他文章的结论一致
容器还是虚拟机和 LXC 常用服务页面给出的建议也是"推荐用一台虚拟机跑 Docker"。这篇文章展开讲清楚背后的技术原因,不是重复下结论。
技术上能不能跑
能,但需要打开几个开关,而且有明确的限制。
需要开启的选项
| 选项 | 作用 | 代价 |
|---|---|---|
Nesting | 允许容器内再跑一层容器隔离,Docker 依赖它 | 会把宿主机的部分 procfs/sysfs 内容暴露给容器 |
keyctl | 允许 keyctl() 系统调用,Docker 需要它 | 仅非特权容器需要单独开;开了之后和 systemd-networkd 有冲突(见下) |
sh
# 在宿主机 shell 执行
pct set <ctid> --features nesting=1,keyctl=1keyctl 和 systemd-networkd 二选一
官方 pct.conf 文档原话:"本质上你需要在运行 systemd-networkd 和运行 Docker 之间二选一"——因为 systemd-networkd 在 keyctl() 被拒绝时会把它当成致命错误。如果容器的网络管理用的是 systemd-networkd,开 keyctl 之后可能需要换成别的网络管理方式。
cgroup 版本要求
Docker 需要 cgroup v2。PVE 9 已经完全移除了 cgroup v1,所以容器的系统本身也要支持 cgroup v2(systemd 231 或更新版本),老旧的容器模板可能跑不起来。
代价:为什么官方不首推这条路
| 方面 | 说明 |
|---|---|
| 隔离性叠加变弱 | 非特权容器本身的隔离依赖 UID 映射这一道墙;Docker 又在容器内部叠加了一层自己的隔离(namespace、cgroup),两层嵌套让排查问题和评估风险都更复杂 |
| 不能在线迁移 | LXC 容器本身就不支持在线迁移,跑了 Docker 之后这个限制依然存在,而 Docker 场景往往更需要迁移能力(滚动升级、故障转移) |
| 存储驱动的坑 | Docker 常用的存储驱动在嵌套环境下不一定按预期工作,遇到问题时不容易判断是 Docker 本身的问题还是 LXC 嵌套导致的 |
| 网上教程默认假设是普通 Linux | 绝大多数 Docker 相关的教程和排错文章都假设跑在一台完整的虚拟机或物理机上,遇到 LXC 特有的问题(cgroup、AppArmor、keyctl)时可参考的资料明显更少 |
| AppArmor 限制 | 部分容器化工作负载需要放开 AppArmor 限制才能正常工作,而官方文档明确"关闭 AppArmor 不建议用于生产环境" |
特权容器不是解决这些问题的办法
遇到权限或者能力不足的报错时,把容器改成特权容器几乎总能让问题"消失"——但代价是彻底放弃了非特权容器的隔离边界,容器内的 root 直接等价于宿主机 root(详见特权与非特权容器)。这不是本文讨论的"能不能在 LXC 里跑 Docker"的答案,而是换了一个风险高得多的问题。 遇到这类报错,应该判断是不是真的需要 Docker 这一层,还是回到虚拟机方案。
什么时候可以考虑在 LXC 里跑
- 只跑一两个来源完全可信、你自己审查过的镜像,且能接受出问题需要重建容器。
- 资源非常紧张的小型环境,多花 1–2 GB 内存开一台虚拟机确实吃力。
- 这个服务本身不面向公网、不处理敏感数据,出问题的影响范围可控。
什么时候不要
- 需要在线迁移、需要高可用。
- 会跑你没有完整审查过的镜像(这一条本身就是特权与非特权容器反复强调的边界)。
- 生产环境、面向公网的服务,或者团队里不止你一个人维护这套环境——遇到问题时"网上教程能不能直接抄"的差距会被放大。
这些场景应该直接用一台虚拟机跑 Docker,参考 Docker 宿主虚拟机。
如果决定要做
简要步骤(默认使用非特权容器):
- 按创建第一个容器建一个容器,保持 Unprivileged 勾选。
- 打开 Nesting 和 keyctl:
sh
# 在宿主机 shell 执行
pct set <ctid> --features nesting=1,keyctl=1- 容器内按 Docker 官方文档安装:
sh
# 在容器内部执行(Debian/Ubuntu 示例,具体步骤以 Docker 官方文档为准)
curl -fsSL https://get.docker.com | sh别用来路不明的一键脚本
装 Docker 只从 Docker 官方文档给出的方式安装,不要用搜到的第三方脚本——这条规则和「模板只用官方源」是同一个道理。
检查结果
sh
# 在容器内部执行
docker run --rm hello-world能正常输出说明基本可用。再确认一下存储驱动:
sh
# 在容器内部执行
docker info | grep -i "storage driver"如果不是 overlay2 或者启动报错,说明存储驱动在这层嵌套里没有按预期工作,这类问题排查成本较高,是本文前面提到的"代价"之一的具体体现。
常见问题
docker run 报 proc 相关的挂载错误。 通常是 Nesting 没开。确认 pct config <ctid> 里 features 一行包含 nesting=1。
容器里 systemd-networkd 报错,服务起不来。 大概率是开了 keyctl 之后触发了前面提到的冲突,需要换一种网络管理方式,或者重新评估是否真的需要在这个容器里跑 Docker。
存储驱动报错,或者容器内 Docker 卷行为异常。 这是嵌套容器已知的高发问题区域,遇到反复出现的存储驱动问题,是转向虚拟机方案的一个明确信号,而不是继续深挖配置。
只是想跑个 Docker Compose 管理的小服务,值不值得为此开一台虚拟机? 如果服务数量不多、都是你完全信任的镜像,也可以考虑用 OCI 镜像建容器——它不涉及嵌套 Docker,但目前是技术预览且没有增量更新能力,两条路各有取舍,都不是"完美方案",选哪个取决于你更在意稳定性还是资源开销。