Skip to main content
当评估任务要求 Agent 执行 docker builddocker rundocker compose 时,先按任务的信任边界 选择 Docker access: 完整的 Docker profile 依赖 Linux cgroup v2、systemd 和支持 project quota 的文件系统。NixOS 可以用 NiceEval module 部署;Ubuntu、Debian 与其它 systemd Linux 需要安装等价的宿主服务。 macOS 上的实际选择是 OrbStack 或 Apple container
  • OrbStack 的 Docker engine 可以用于可信任务的 Docker socket 模式。需要完整 profile 时,在 OrbStack Linux machine 内运行 NiceEval,并按该发行版的 Linux 安装方式部署宿主服务。
  • Apple 官方 container 在 Apple silicon 和 macOS 26 上把 Linux container 作为轻量 VM 运行。当前 NiceEval Docker Provider 不操作它的 CLI 或 API,因此不能直接选择 container;这需要独立 Provider。
Apple container 使用 Containerization framework,不是 containerd。直接安装 containerd 也不会让 Darwin 获得 Linux cgroup 或 project quota;它仍需要 Linux VM。 三种模式都要求镜像预装 Docker CLI。两种 DinD 模式只接受从官方 docker:<version>-dind 派生的兼容镜像。镜像需要 Docker daemon、Node 和评估工具, 但不需要自定义 NiceEval ENTRYPOINT。NiceEval 负责启动与监督 inner daemon、 Sandbox 保活、容器内 TTL 和 docker info 探活,不会向任意镜像动态安装 Docker。 不能直接把未经派生的 docker:<version>-dind 写成 source.image。该镜像没有 NiceEval supervisor 需要的 Node,也没有被测 Agent 的工具。创建时会以 dind-image-incompatible: missing node 失败。请使用下面的 Dockerfile,或先发布等价的派生镜像, 再让 source 引用它。

方案一:直接挂 Docker socket

先准备只有 CLI、Node 和评估工具的镜像:
sandbox/Dockerfile
在 Experiment 里显式填写宿主 Unix socket。NiceEval 不会从进程环境或 Docker context 猜这个路径:
experiments/docker-socket.ts
NiceEval 会解析 symlink、确认最终目标是 Unix socket,把它挂到 Sandbox 内固定的 /var/run/docker.sock,并按 socket 的数值 GID 给 node 添加补充组。启动 Agent 前,NiceEval 会确认 镜像没有预设 Docker endpoint 或 context,而且默认 docker info 与显式访问这个 Unix socket 得到同一个 daemon。CLI 缺失、用户无法访问 socket 或镜像把默认 endpoint 改到别处时,Sandbox 创建失败。 这项检查只固定 Agent 启动时的默认用法,不是安全边界。Agent 之后仍可显式传 docker --host 访问另一个 endpoint。不可信任务需要依赖 managed 模式的网络策略,而不是依赖 Docker CLI 的默认值。
这不是隔离方案。Agent 可以通过 socket 创建 privileged 容器、挂宿主目录,并操作同一 daemon 上的 其它容器、image、volume 和 network。不要给不可信 Agent 挂 rootful 宿主 socket。

方案二:Docker-in-Docker

DinD 让每个外层 Sandbox 拥有自己的 inner daemon。先创建镜像:
sandbox/Dockerfile
生产评估应把 FROM 钉到审核过的 digest,并把被测 Agent 需要的固定 CLI 烘焙进镜像。

NiceEval 如何启动 DinD

选择 dockerAccess: { mode: "dind", ... } 就同时选择了 NiceEval 的 DinD 镜像协议。 provider 会覆盖派生镜像的原有 ENTRYPOINTCMD,先检查 docker-initnodedockerdockerd-entrypoint.shtimeouttail,再启动自己的 supervisor。supervisor 同时监督官方 dockerd-entrypoint.sh dockerd与 Sandbox 保活进程。 任一进程提前退出都会停止 outer container,而不会留下一台看似存活但无法使用 Docker 的 Sandbox。 inner daemon 只监听 /var/run/docker.sock,不开放 2375/2376 TCP endpoint。 Agent 仍以 user: "node" 运行。上面 Dockerfile 在构建期把 node 加入 docker 组,因此不需要 chown root:nodechmod 666 socket。如果镜像缺少工具、 用户不在 docker 组、daemon 提前退出或 readiness 超时,NiceEval 会在删除创建失败的 容器前收集有界日志尾部并报出可操作的原因。 派生镜像不要设置 DOCKER_HOSTDOCKER_CONTEXT。NiceEval 会先确认默认 context, 再核对不带 endpoint 选项的 docker info 与显式 /var/run/docker.sock 是否到达同一个 daemon。 这项 compatibility check 通过后,才会执行作者声明的 readiness。 这个协议不承诺保留任意 service image 的启动语义。如果你要评测一个必须依赖自己 ENTRYPOINT / CMD 的服务组,应该使用 Compose Sandbox,由 Compose 声明服务进程。

用 Action 准备 Attempt 运行时

Dockerfile 只烘焙固定工具和项目初始文件。需要 inner daemon 的操作放进 Sandbox 级 .before(action),不要放进镜像 ENTRYPOINT。下面这个声明式 Action 会创建可写 workspace 并验证 inner daemon;只有镜像确实烘焙了对应归档和文件时,才在这里加入 docker load、文件复制或项目 smoke-check 命令:
lib/dind-sandbox.ts
NiceEval 会先启动并验证 inner daemon,再执行这个 action,最后运行 Agent。命令非零退出时, Attempt 在 sandbox.create 阶段记为 errored,不会把半成品 Sandbox 交给 Agent。固定内容留在 image layer, 每个 Attempt 只复制或导入必须写入 tmpfs 或 inner data-root 的状态,可以减少重复安装和网络漂移。
Docker Profile 不能保存默认的 sandboxState.all。只有某个 Action 的全部副作用都局限于已经静止的 inner /var/lib/docker,并且 profile 已具备独立、完整预分配、固定大小的 image slot 与 seed, Provider 才会为显式 sandboxState.dockerData 发布覆盖范围。shared loop 或 project-quota Profile 继续显示 Unsupported,让该 Action 和后续 Action 真实执行。

Raw privileged DinD

在一次性 VM 或专用 runner 上,可以显式选择 raw privileged:
experiments/dind-raw.ts
必填的 raw-privileged 字面量是风险授权。NiceEval 不会把 raw 模式描述成 rootless,也不会在失败时 自动改用 managed profile。storageProfile 为 inner /var/lib/docker 分配磁盘配额;不要把该路径放进 tmpfs。这个 profile 只增加磁盘准入、配额和强杀恢复,不会把 raw privileged 变成安全隔离方案。

在 macOS 上使用 OrbStack

可信的本地评估可以复用 OrbStack Docker engine。先让 OrbStack 创建 Docker context,再读取实际 Unix socket:
把输出的 unix:// 路径去掉 scheme 后填入 dockerAccess.socketPath。这仍是 Docker socket 模式;Agent 可以完全控制 OrbStack daemon,不适合不可信任务。 需要 managed profile 时,使用 OrbStack 的完整 Ubuntu machine,而不是把 profile 服务装进 macOS:
进入 machine 后,按 Ubuntu 的宿主安装流程准备 systemd、cgroup v2 和 project-quota 文件系统,并在该 machine 内运行 NiceEval。Ubuntu 安装包发布前,这条路径不是开箱即用能力;不要用 OrbStack Docker engine 冒充 managed profile。

Managed rootless DinD

共享宿主或不可信 Agent 使用 managed 模式。它在 inner daemon 之外增加 profile attestation、单容器 资源限制、跨进程容量准入、独占外层网络和 watchdog 恢复: NixOS 宿主先按照在 NixOS 上配置 Managed DinD部署并验证 profile, 再配置下面的 Experiment。 下面假设 Dockerfile 已把固定的 runtime 归档复制到 /opt/images/runtime.tarload-fixed-runtime 只写 inner /var/lib/docker,所以它准确声明 sandboxState.dockerData。如果 Action 还写 workspace、 home、tmpfs、挂载或外部系统,就不能使用这个 state。
experiments/dind-managed.ts
profile 缺失、拼错、attestation 失败或容量不足时,NiceEval 会在模型调用前失败,绝不降级成 raw privileged。宿主部署完成后先检查 profile: dockerDataBytes 是每个 Attempt 的 inner Docker data 硬上限。watchdog 从磁盘配额池中授予一份私有 allocation,并把它挂到 /var/lib/docker。Docker image layer 与 BuildKit cache 因而占磁盘,不计入宿主 Shmem;workspace、/tmp 等确实需要内存语义的路径仍可使用有界 tmpfs
doctor 会在该 profile 上启动一次 DinD 容器,并实际运行内层 docker run --rm alpine:3.20 true。它证明 profile 支持 nested Docker,但不会检查项目自己的 Dockerfile。项目镜像仍由默认 docker info readiness 验证。

为 DinD 设置运行并发

resources.memoryBytes 限制一台 Sandbox,dockerDataBytes 限制它的 inner Docker data。profile 会对 所有 NiceEval 进程执行共享容量准入;当前没有足够的内存或磁盘 allocation 时,新 Attempt 会等待。先用 较小并发完成 smoke run:
再根据宿主的可用内存、CPU 和磁盘吞吐逐步增加。profile 的总预算负责阻止多个 Experiment 合计超过宿主 容量,maxConcurrency 仍负责限制单次 Run 的并行度。两者不能互相替代。

定位 DinD 创建失败

创建失败后使用终端给出的 Attempt 定位符,不要直接翻运行产物:
常见错误与处理方式:

在评估用例里验证 Docker 任务

三种模式对评估用例暴露相同的 Docker CLI 用法:
evals/docker-compose.eval.ts
Socket 模式的命令操作显式选择的外层 daemon。两种 DinD 模式操作 Sandbox 内的 inner daemon。 sandbox 也可以直接写在 defineEval({ sandbox: ... }) 里。多条评估用例共用配置时写在 Experiment 上。