Skip to content

feat(sandbox): Docker 后端改为会话级长驻容器,对齐 E2B 语义 - #59

Draft
lyingbug wants to merge 5 commits into
mainfrom
cursor/docker-sandbox-backend-research-1e82
Draft

feat(sandbox): Docker 后端改为会话级长驻容器,对齐 E2B 语义#59
lyingbug wants to merge 5 commits into
mainfrom
cursor/docker-sandbox-backend-research-1e82

Conversation

@lyingbug

@lyingbug lyingbug commented Aug 12, 2026

Copy link
Copy Markdown
Owner

背景

原来的 docker 后端每次执行都是 docker run --rm 加一个只读 bind mount,和 E2B 不是「能力少一点」而是模型不同
没有会话状态,shell_exec、附件暂存、产物收集在能力矩阵里根本不注册;超时 kill 的只是 CLI 客户端进程,
容器还在跑;/workspace 只读,而 skills 框架恰恰要往 /workspace/output 写。

本 PR 把它重写为 RemoteSandboxClient 适配器:一个会话一个长驻容器,直接打 Docker Engine API。
session→sandbox 绑定、生命周期锁、能力矩阵、产物收集一行没改——这正是当初抽出这层的收益。

调研过程与「Docker 到底能不能做到」的实测证据保留在 docs/poc/docker-sandbox(独立 go module,不参与主构建)。

实现要点

  • 生命周期:Create/Connect/Get/List/Delete 映射到容器,metadata 落成 labels。
    Connect 会重启被停掉的容器——文件系统还在,会话就能接着用(对应 E2B 的 auto-resume)。
  • 超时由容器内的 timeout(1) 执行:取消 HTTP 请求不会终止容器里的进程。命令通过位置参数
    (sh -c '…' weknora-exec "$@") 传入,不做字符串拼接,脚本里的引号和换行不会改变实际执行的东西。
  • 空闲回收:daemon 没有 TTL。exec 的 wrapper 顺手 touch 一个活跃标记文件,清扫时一次
    HEAD /archive 读 mtime 即可判断闲置,无需额外 exec 或 Redis 记账;按 daemon 端点限流、后台执行;
    每个容器把创建时的 TTL 记在 label 上,避免 A 配置的清扫拿自己的 TTL 衡量 B 配置的容器。
  • 文件面走 archive API,mkdir/rm/ls 用 exec 补齐(Engine API 无对应接口)。
  • 安全基线CapDrop: ALL + 仅补回 root 装包所需的 7 个 cap、no-new-privileges、内存/CPU/PID 上限、
    swap 关闭;远程 daemon 走 mTLS,证书按路径引用不入库;daemon 地址过 SSRF 校验。
  • 镜像即模板ListTemplates/EnsureStandardTemplate 走镜像列举与后台拉取,设置页流程不变。
  • 配置面:新增 daemon 地址、TLS 证书目录、CPU/内存/进程数上限、网络模式、runtime、空闲 TTL。
  • 标准镜像预建并授权 /workspace/{input,output},并补上 curl(连通性检查用它探测出网,文档一直声称镜像里有)。

边界(写进文档,不是遗漏)

跨主机调度、内核级隔离、内存态快照、域名级出网策略、卷挂载都不在这个后端的能力范围内,
需要这些仍应使用 E2B 协议后端。

验证

真实 daemon 一致性测试(与 E2B 一致性测试断言同一批语义):

$ DOCKER_INTEGRATION_IMAGE=wechatopenai/weknora-sandbox:dev \
  go test -tags=docker_integration ./internal/sandbox -run '^TestDocker.*Integration' -v
--- PASS: TestDockerBackendConformanceIntegration/SessionScopedStatePersistsAcrossExecutions
--- PASS: TestDockerBackendConformanceIntegration/InstalledPackagesSurviveBetweenExecutions
--- PASS: TestDockerBackendConformanceIntegration/ShellExecSharesTheSessionSandbox
--- PASS: TestDockerBackendConformanceIntegration/AttachmentStagingAndArtifactCollection
--- PASS: TestDockerBackendConformanceIntegration/TimeoutIsReportedAsKilled
--- PASS: TestDockerBackendConformanceIntegration/TimeoutActuallyStopsTheProcess
--- PASS: TestDockerBackendResumesStoppedContainerIntegration

端到端 UI:在本地跑起 WeKnora(Lite),新建 Docker 沙箱后端并执行「完整验证」,
五项全绿(含在真实容器里执行命令与出网探测)。

docker_sandbox_backend_deep_check_all_pass.mp4

Docker 后端的运行参数
完整验证五项全绿

单元测试用内存版 Engine API 驱动适配器(不需要 daemon);go vet + 全量 go test
前端 type-check 与 i18n 审计均通过。

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

lyingbug and others added 4 commits August 12, 2026 20:19
现有 docker 后端每次执行都是一次性 docker run --rm,没有会话状态、超时不终止负载、
工作目录只读,与 E2B 后端不是能力差异而是模型差异。

新增一份直接打 Docker Engine API 的 PoC(29 项检查全过)验证:容器可承载
RemoteSandboxClient 的全部方法,commit 可承载空间级快照与增量更新;同时复现
Docker 无法对齐的部分(无空闲 TTL、客户端取消不杀进程、快照不含内存态、
CapDrop 后 root 越不过权限位、kill docker run 会留下在跑的容器)。

调研结论与落地建议见 docs/sandbox-docker-backend.md。

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
把 docker 从一次性 `docker run --rm` 重写为 RemoteSandboxClient 适配器,
直接走 Docker Engine API,一个会话一个长驻容器,因此 shell_exec、附件暂存、
产物收集这些会话级能力对 docker 与 E2B 一致。

- Create/Connect/Get/List/Delete 映射到容器生命周期,metadata 落为 labels;
  Connect 会重启被停掉的容器,保住会话文件系统
- Exec 用容器内 timeout(1) 包裹:客户端取消不会终止容器内进程
- 文件面走 archive API,mkdir/rm/ls 用 exec 补齐(Engine API 无对应接口)
- daemon 没有空闲 TTL,新增按活跃标记文件回收的限流清扫
- 镜像即模板:ListTemplates/EnsureStandardTemplate 走镜像列举与后台拉取
- 租户配置新增 host/TLS/CPU/内存/PID/网络/runtime/空闲 TTL 字段
- 标准镜像预建并授权 /workspace/{input,output}

针对真实 daemon 的一致性测试(-tags=docker_integration)覆盖会话状态、
包安装存活、shell_exec 复用沙箱、附件与产物、超时真实终止进程、
容器被外部停掉后恢复。

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
新增 daemon 地址、TLS 证书目录、空闲回收、CPU/内存/进程数上限、网络模式字段,
并把 docker 已经是会话级后端这件事同步到各处文档。

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
沙箱连通性检查用 curl 探测出网,而标准镜像里没有它——docker 后端每次完整验证
都会以「curl: not found」失败。文档一直把 curl 列在镜像内置工具里,这里让镜像
与文档一致。

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
@cursor cursor Bot changed the title docs(sandbox): Docker 沙箱后端重做调研(含可复现 PoC) feat(sandbox): Docker 后端改为会话级长驻容器,对齐 E2B 语义 Aug 12, 2026
空闲清扫按活跃标记文件的 mtime 判断容器是否闲置,而技能脚本以非 root 的沙箱账号
执行。标记由容器入口创建并 chmod 666,否则在 umask 022 的宿主机上,只跑脚本的会话
会被判成从未活动、在用户正用着的时候被回收。

一致性测试直接以沙箱账号 exec 并断言标记权限与 mtime 推进,不再依赖
「每次执行前恰好有一次 root 操作」这一隐式耦合。

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant