在 smolvm 虚拟机内运行 Docker 守护进程:为 Testcontainers、Compose 与 Agent 提供容器能力

发布时间:2026/10/9 4:45:27
在 smolvm 虚拟机内运行 Docker 守护进程:为 Testcontainers、Compose 与 Agent 提供容器能力
虚拟化AI Agent人工智能CLI【免费下载链接】smolvmAn embeddable, portable, branchable virtual machine to safely run Agents locally.项目地址https://gitcode.com/gh_mirrors/sm/smolvm点击查看免费下载本文档基于 smolvm 仓库的docs/docker-in-machine手册含 SKILL 流程、预检/创建/启动/验证脚本与陷阱记录编写介绍如何在 smolvm 机器内部运行一个完整的 Docker 守护进程dockerd供那些必须自己调用 Docker 的软件使用——例如启动容器的测试套件Testcontainers、Compose、镜像构建或是自行拉起容器的 Agent。读完本文你将掌握机器内 Docker 的完整搭建流程、/storage数据盘的硬性要求、init只运行一次导致的绑定挂载陷阱以及如何通过 vsock 把 guest 内的 docker.sock 安全地暴露给宿主机。为什么需要机器内的 Dockersmolvm 本身启动 OCI 镜像不需要 Docker它直接以微虚拟机方式引导 OCI 镜像。因此机器内 Docker 的唯一适用场景是——机器内部的软件必须调用 Docker 自身。典型例子包括Testcontainers测试代码在测试进程内通过 Docker API 拉起一次性容器docker compose在机器内编排多容器服务镜像构建在机器内执行docker build编码 AgentAgent 代码需要自行启动、管理和销毁容器。用一句话概括smolvm 负责把整台机器一个隔离的 VM 内核跑起来而机器内的 Docker 负责把机器里的容器跑起来。两者各司其职Docker 的守护进程和容器始终留在 guest 内核内部不会碰宿主机的 Docker。注意如果只是要运行 OCI 镜像直接用 smolvm 原生引导即可完全不需要本文这套流程。第一个硬性前提Docker 数据必须放在/storage这是硬性要求不是偏好。原因在 smolvm 的存储架构里写得很清楚机器根文件系统本身已经是一个 overlayrootfs overlay该 overlay 以 initramfsramfs作为 lower layerramfs 不支持 file-handle无 exportfs这向上传播到整个 rootfs overlayoverlayfs 因此拒绝把 rootfs overlay 上的目录用作 Docker 嵌套 overlayoverlay2的 upper 层——这正是 Docker 的overlay2驱动无法把 upper layer 放在 rootfs 上的原因/storage是机器的 ext4 磁盘/dev/vda支持 file-handleoverlay2的层可以正确嵌套。有两种等效写法。第一种是直接指定数据根目录dockerd --data-root/storage/docker --storage-driveroverlay2第二种本文 Smolfile 采用的方式是把/storage/docker绑定挂载到/var/lib/docker这样对假设默认路径的容器工具更友好mkdir -p /storage/docker /var/lib/docker mount --bind /storage/docker /var/lib/docker两种方式都能工作。但请注意docker info不会告诉你最终落在了哪个文件系统上——它只会打印/var/lib/docker这个挂载点路径。真正需要检查的是底层设备smolvm machine exec --name name -- sh -c df /var/lib/docker | tail -1 # /dev/vda 20623316 412 20606520 0% /storage两个读写都显示/var/lib/docker为挂载点路径本身说明不了任何问题设备才是关键值。不只是 Dockercontainerd 也需要同样的处理/var/lib/containerd保存 snapshotter 的 overlay 状态如果留在 rootfs overlay 上会以完全相同的方式失败。docs 站点的指南guides/docker-in-a-machine.mdv1.14.6 时只绑定了/storage/docker漏掉了/storage/containerd——containerd 的 overlay 挂载会被拒绝并报invalid argument容器根本无法启动。上游 Smolfile 和本文的 Smolfile 都同时挂载两个目录见 examples/docker-in-vm/docker.smolfile 与 docs/docker-in-machine/assets/docker.smolfile。这也解释了为什么 Smolfile 必须声明storage 2020 GiB 的 ext4 数据盘以及为什么这里所有检查都针对/dev/vda。第二个陷阱init只运行一次绑定挂载在第二次启动时消失这是破坏第二次会话的陷阱也是这个 packet 需要单独的start-dockerd.sh而不是只靠一个 Smolfile 的原因。init在machine create时被声明但只在首次启动时执行一次。smolvm machine create --help对--init的描述是 Run command on every VM start而实际语义是每个 VM 的首次启动执行一次——docs/smolfile.md 中明确说明initruns once类似 Dockerfile 的RUN。因此第一次启动init运行apk add docker、创建目录、执行两个mount --bind一切正常停止再启动后init不再运行绑定挂载消失/var/lib/docker重新落回 rootfs overlaydockerd要么拒绝启动要么运行在错误的文件系统上。实测复现macOS arm64 与 Linux aarch64v1.14.2 / v1.16.1 / v1.18.2 / v1.22.2Init already completed, skipping 5 command(s) NO_BIND_MOUNTS_AFTER_RESTART DOCKERD_DOWN运行 docs/docker-in-machine/scripts/start-dockerd.sh 之后一切恢复而且docker images仍能看到alpine:latest——因为镜像在/storage上。所以这个陷阱的失败模式是daemon 起不来或daemon 跑在错误的文件系统上不是数据丢失。生产级做法把挂载重放与 daemon 启动合并在同一条命令里正确的恢复方式不是手动补挂载而是让启动 dockerd这条命令先重放挂载再启动 daemon。start-dockerd.sh在 guest 内执行的正是上游示例的 Start dockerd recipemkdir -p /storage/docker /var/lib/docker /storage/containerd /var/lib/containerd mountpoint -q /var/lib/docker || mount --bind /storage/docker /var/lib/docker mountpoint -q /var/lib/containerd || mount --bind /storage/containerd /var/lib/containerd rm -f /var/run/docker.pid dockerd --storage-driveroverlay2 /tmp/dockerd.log 21 for i in $(seq 1 60); do docker info /dev/null 21 break; sleep 1; done docker info 2/dev/null | grep -E Server Version|Storage Driver|Docker Root Dir要点先rm -f /var/run/docker.pid清掉残留 pid 文件mountpoint -q ... ||保证幂等重复执行不会重复挂载60 秒轮询等docker info应答两个验证主机上都是几秒内应答余量覆盖首次启动需要创建存储布局的情况日志写入/tmp/dockerd.logdaemon 起不来时可用smolvm machine exec --name name -- tail -20 /tmp/dockerd.log排查。完整操作流程本 packet 的全部脚本都在 docs/docker-in-machine/scripts/ 下机器名默认smolskill-docker必须以smolskill-开头cleanup.sh才会删除它。第 1 步预检scripts/preflight.sh该脚本只读不启动 VM、不写 smolvm 状态输出一行一个keyvalue便于解析docker_in_machineverifiedmacOS / Linuxdocker_in_machineunavailableWindows带原因同时检查二进制是否存在、版本是否与验证版本 v1.22.2 匹配、加速器访问macOS 上kern.hv_support、Linux 上/dev/kvm的读写权限、macOS 的 socket 路径长度HOME过深会导致每次 VM 启动失败报krun_start_enter -22。第 2 步创建并安装scripts/create-docker-machine.sh # name 默认 smolskill-docker脚本执行smolvm machine create --name name -s assets/docker.smolfile --net-backend virtio-net记录机器名然后machine start并验证docker --version。apk add docker发生在首次启动start时而不是 create 时并且占据了绝大部分时间。create 本身只把 Smolfile 变成机器定义真正的软件安装拉取 Alpine 包、安装 daemon CLI在 init 阶段完成。机器定义全文docs/docker-in-machine/assets/docker.smolfilecpus 2 memory 2048 net true # Docker 数据目录必须在 /storageext4不能在 rootfs overlay 上。 # 硬性要求rootfs overlay 以 initramfs(ramfs) 为 lower layer # ramfs 无 file-handle 支持overlayfs 拒绝把它作为嵌套 overlay 的 upper dir。 storage 20 docker_socket true init [ apk update -q, apk add docker -q, mkdir -p /storage/docker /var/lib/docker /storage/containerd /var/lib/containerd, mount --bind /storage/docker /var/lib/docker, mount --bind /storage/containerd /var/lib/containerd, ]注意--net-backend virtio-net机器内 Docker 的容器网络docker0 桥接以及发布端口都依赖 virtio-net 后端。第 3 步启动 daemon每次启动都要跑不只是第一次scripts/start-dockerd.sh成功输出示例Server Version: 25.0.5 Storage Driver: overlay2 Docker Root Dir: /var/lib/docker server_version25.0.5 resultdockerd_up第 4 步证明它真的可用而且落在正确的文件系统上scripts/verify-docker.shstorage_driverok (overlay2) docker_root_deviceok (/dev/vda) pulldocker.io/library/alpine:latest nested_containerok (NESTED_OK) host_networkok (HOSTNET_OK) host_socketpresent (.../vms/hash/docker.sock) resultdocker_ok验证脚本的每个断言都值得展开docker_root_device是最重要的检查。docker info成功不代表/var/lib/docker在正确的文件系统上——当它坐在 rootfs overlay 上时docker info照样成功随后而来的失败overlayfs 拒绝 ramfs-backed rootfs 作为嵌套 overlay 的 upper dir非常令人困惑而且发生得很晚。验证脚本断言的是底层设备/dev/vda而不是路径先单独docker pull -q alpine再docker run首次运行一个镜像时pull 的进度条与容器自身输出交织在一起而且两个流在不同主机上到达顺序不同Linux aarch64 上 marker 是最后一行macOS arm64 上不是。先 pull 再 run容器输出干净独立又不丢弃能解释真实失败的 stderrnested_containerok证明嵌套容器机器里的 Docker 里的容器能跑host_networkok证明--networkhost可用——Testcontainers 和 Compose 常用它host_socketpresent断言宿主机侧 docker.sock 已创建。第 5 步使用它并在不再需要时清理一台别人要的机器就是交付物让它保持运行把机器名告诉对方。对方的代码可以这样进入机器smolvm machine cp file smolskill-docker:/workspace/file # 或在 create 时用 -v 挂载 smolvm machine exec --name smolskill-docker -- ...宿主机上没有docker客户端时调用 Docker 的代码在机器内运行、指向这台 daemon 即可。清理scripts/cleanup.sh --purgecleanup.sh的行为设计得很保守只删除它记录在状态文件里的smolskill-前缀机器绝不碰你手动创建的其它机器删除使用--force --cascade从 v1.17.0 起非终端 stdin 下不带--force的 delete 会退出 1更早版本会退出 0 却把 20 GiB 的机器留在后面短暂机器ephemeral的条目在 run 返回后才会退役所以脚本先轮询最多 20 秒、打印waitingup to 20s再断言machinesclean脚本只包装公开 CLI不修改任何 smolvm 配置和~/.smolvm下的内容。从宿主机访问 daemonvsock 桥接的 Unix socket 是首选在 Smolfile 中设置docker_socket true或 create 时传--docker-socketsmolvm 就把 guest 的/var/run/docker.sock通过vsock桥接到机器数据目录下的docker.sock宿主路径。machine run --docker-socket也会打印该路径。使用方式D$(smolvm machine>dockerd --hosttcp://0.0.0.0:2375 --tlsfalse \ --data-root/storage/docker --storage-driveroverlay2 \ /tmp/dockerd.log 21 smolvm machine create --name docker -p 12375:2375 ... DOCKER_HOSTtcp://127.0.0.1:12375 docker ps注意事项dockerd在无 TLS 绑定 TCP 时故意睡约 15 秒除非显式传--tlsfalseTCP Docker API 没有自带认证只允许发布到宿主机回环地址127.0.0.1绝不能在共享主机上开0.0.0.0或在前端终止 TLS因此首选 Unix socket 端点socket 本身就是信任边界没有开放 TCP 端口、没有未认证的网络面无需 TLS 或防火墙。端口必须在 create 时钉死宿主到 guest 的端口在create 时声明。Compose 或 Testcontainers 之后动态选择的端口不会自动穿过外层 VM 边界发布所以宿主机必须能访问的端口必须预先 pin 好ports [12375:2375]端口映射支持单端口8080、显式映射8080:80或等长一对一区间5173-5180:5173-5180一台机器最多发布 64 个具体映射未知键在 create 时直接报错而不是静默忽略见 docs/smolfile.md。安全默认值以及为什么它们是默认值docker_socket true是这份配置里唯一把真实权限交给 VM 之外的一行。宿主机上能打开该 socket 的进程可以在机器内启动容器、挂载机器能看到的路径、读取其中的一切。除非宿主机工具确实需要它否则关掉它——本 packet 自己的所有检查都不依赖这个开关机器内的 Docker daemon 就是机器内的 root这正是目的所在。VM 边界让它变得可接受按 docs/security-model.md 的说法把 guest 内的 root 视为不可信每个转发的挂载、端口和网络权限都成为嵌套容器可达范围的一部分guest 内的--networkhost是 guest 自己的网络不是你的。本 packet 特意验证它Testcontainers 和 Compose 常用它的隔离由 VM 提供而非 Docker 提供net true是整台机器的出站访问本场景必须开apk add docker和每次docker pull都需要。注册表集合已知时用--allow-host收窄范围脚本只包装公开 CLI不编辑任何 smolvm 配置和~/.smolvm下的东西清理只删除它记录在smolskill-前缀下的名字。平台支持现状macOS arm64v1.22.2原样发布版全部检查通过含重启陷阱及其恢复Linux aarch64v1.18.2 及 v1.22.2 各一次Smolfile 的memory需降到1024 MiBLima 宿主在固定 30 秒就绪窗口内无法引导更大的 guestinstallpacket 的 traps 里有数据。在小内存或繁忙宿主上先降memory再怀疑 Docker——1024 MiB 足够dockerd加一个嵌套 alpine 容器Linux x86_64本用例未在任何宿主上跑过Windows x86_64到 v1.22.2 为止不可用。原因在捆绑 guest 内核不在操作流程——没有任何用户可及的配置能绕过。详见 docs/docker-in-machine/references/windows.md。Windows 为什么不行内核缺失两项能力Windows 的 libkrunfw 构建缺少两个内核选项桥接网络默认配置下dockerd直接拒绝启动报error creating default bridge network: operation not supported直接探测ip link add name testbr0 type bridge得到RTNETLINK answers: Not supportedPOSIX 消息队列用--bridgenone强制启动后 daemon 表面健康这就是看起来成功的假象但创建任何容器都会在/dev/mqueue挂载上失败mount mqueue:/dev/mqueue ... : no such device。直接对照内核配置即可确认config-libkrunfw-windows_x86_64带有# CONFIG_POSIX_MQUEUE is not set和# CONFIG_BRIDGE is not set而 Linux 的config-libkrunfw_x86_64两者都是y。健康 daemon 不是终点线——--bridgenone把 dockerd 拉起来了容器仍然创建不了。重新验证新内核版本时不需要拉 dind 镜像用普通alpineguest 直接探测两个内核特性按退出码判断不要读消息文本ip link add name testbr0 type bridge ; echo bridge$? mount -t mqueue none /dev/mqueue ; echo mqueue$?两个非零 内核仍缺这两项本结论继续成立两个零 guest 内核已变化上面所有内容需要重新跑。该探测在 v1.22.2Windows 11 Home build 10.0.26200 UBR 9457guest 内核 6.12.95上再次给出RTNETLINK answers: Not supported和mounting mqueue on /dev/mqueue failed: No such device结论不变。从源码与测试看实现依据以上所有结论都能在仓库中找到对应的实现证据机器定义与双重 bind mountdocs/docker-in-machine/assets/docker.smolfile 与带完整注释的上游版 examples/docker-in-vm/docker.smolfile后者还记录了内核要求CONFIG_OVERLAY_FS、CONFIG_NETFILTER、CONFIG_NF_TABLES、CONFIG_NFT_COMPAT、CONFIG_BRIDGE以及 containerd 状态根/var/lib/containerd的独立处理原因挂载重放与 daemon 启动docs/docker-in-machine/scripts/start-dockerd.sh文件系统断言、先 pull 后 run 的验证顺序docs/docker-in-machine/scripts/verify-docker.sh平台与加速器预检、Windows 判定docs/docker-in-machine/scripts/preflight.sh陷阱清单挂载失效、docker info的误导性、containerd 双挂载、machine delete需--force、docs 站指南与 packet 的差异docs/docker-in-machine/references/traps.mdWindows 内核探测记录docs/docker-in-machine/references/windows.mdinit运行一次语义docs/smolfile.md。未验证事项如实说明v1.14.2 之后未在 Windows 上实际运行过dockerd本身——v1.22.2 的结论来自内核探测而非 daemon 运行Linux x86_64 未跑过本用例宿主机侧 docker.sock 未被宿主docker客户端驱动过——socket 被创建并被断言存在但两个验证宿主都没有 docker 客户端Compose 和 Testcontainers 本身未运行——packet 验证的是它们依赖的基础docker info、嵌套容器、host 网络而不是这些工具。相关文档dev-envinit-runs-once 语义的基础install本流程假设的引导方式teardown清理脚本的出处。赞分享虚拟化AI Agent人工智能CLI【免费下载链接】smolvmAn embeddable, portable, branchable virtual machine to safely run Agents locally.项目地址https://gitcode.com/gh_mirrors/sm/smolvm点击查看免费下载相关推荐如何在 code-server 容器内挂载 docker.sock 访问宿主机 Docker 守护进程如何在 code server 容器内挂载 docker.sock 访问宿主机 Docker 守护进程 如果你用容器方式部署了 code server希望在后端开发工具Web终极指南如何在Docker容器中运行macOS虚拟机终极指南如何在Docker容器中运行macOS虚拟机 Docker OSX项目是一个创新的开源工具它允许用户在Docker容器中模拟运行macOS环境。这个虚拟化安全终极指南如何在Docker容器中运行macOS虚拟机终极指南如何在Docker容器中运行macOS虚拟机 想要在Linux系统上体验macOS的魅力吗现在通过Docker容器技术你可以轻松地在任何支持KVM虚拟化上一篇Rematch Models 深度指南定义 state、reducers 与 effects 的完整模型体系下一篇Golem状态迁移技术跨区域数据复制与灾备方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考