如何在自有VPS上自托管Dormice代理沙箱
Dormice可在一台自有Linux VPS上运行E2B兼容沙箱。本文介绍安装、执行不受信任代码、验证隔离效果,并根据实测内存与唤醒数据估算主机规格。
Dormice 是什么,以及它不是什么
Dormice 是一个自托管的代理沙箱:在您拥有的 Linux VPS 上运行一个 daemon,您的代理代码通过 HTTP 调用它,在隔离容器中运行不受信任的代码。程序按名称请求沙箱,无论沙箱之前处于什么状态,都会返回同一个沙箱;随后,程序在其中运行命令并读取输出。沙箱是一种编程资源,不是供您登录的机器。
这与向代理提供整台计算机的方式不同。供编码代理使用的一次性 VM 是一台可通过 SSH 登录的主机,允许代理随意修改,完成后再删除。Dormice 位于更低一层:它是程序在已有代码、需要安全运行环境时调用的执行 API。如果整台机器是工作单元,应使用一次性 VM。如果单个 exec 调用是工作单元,并且您每天需要运行 100 次而不是创建 100 台 VM,应使用 Dormice。
该项目称自身兼容 E2B。E2B 是托管沙箱服务,许多代理框架已经导入其客户端库。Dormice 使用自己的 URL 前缀提供相同协议,因此,指向您自己的服务器后,基于官方 e2b package 编写的应用仍可继续运行。应用代码无需修改。只需修改 2 个 URL 和 1 个 API key 前缀。
“智能体沙箱中的 SQLite”在实践中的含义
SQLite 是嵌入应用中的数据库,不是需要运维的服务。Dormice 直接借用了这一对比。一个守护进程、一个用于记录账本的 SQLite 文件、一个 TCP 端口。不需要 Kubernetes、独立数据库或调度器。守护进程会在账本旁创建锁文件;如果账本与它发现的机器不可能属于同一台主机,它就会拒绝启动,因此不会悄然发生脑裂。该设计只面向单台机器。如果需要跨多台主机管理集群,README 会明确建议您选择其他方案,您应当采纳这一建议。
这个概念的另一半与成本有关。托管沙箱按存在时间的每一秒计费,因此托管沙箱从设计上就是一次性的。Dormice 运行在您已经承担成本的硬件上,因此其沙箱可以长期保留,静置时间越长,平均成本越低。沙箱会逐级冷却:活动、冻结、停止,然后归档。每次 acquire 都会将其从当前所处的级别恢复。
冻结是最值得理解的部分,因为它使永久保留每个智能体的沙箱变得经济可行。以下是项目发布的实测数据,测试使用的是项目自身的硬件,不是您的硬件。
The data behind this chart
[
{
"label": "Active, holding 1 GiB",
"resident_memory_mib": 1024,
"wake_ms": 0
},
{
"label": "Frozen",
"resident_memory_mib": 5,
"wake_ms": 50
}
]一个占用 1024 MiB 内存的空闲沙箱冻结后,常驻内存会降至 5 MiB,并在约 50 ms 内恢复。进程会原地挂起和恢复,因此长期运行的智能体可以在冻结期间保留 shell 状态和未完成的工作。在据此规划容量前,请先在自己的主机上复现这一过程。
安装前主机需要满足的条件
主机必须是 x86_64 架构的 Ubuntu 或 Debian,安装程序需要 root 权限。守护进程运行时仍会保留 root 权限,因为它需要执行 loop 挂载并写入 cgroup。
沙箱通过带有 gVisor 的 Docker 运行。gVisor 是一种容器运行时,会在容器内核与主机内核之间加入用户空间内核,并提供每个沙箱使用的 runsc 运行时。守护进程需要 Node 22 或更高版本。安装程序会自带一份 Node,因此不会修改系统 Node。
系统必须存在 swap,并且 vm.swappiness 必须为 100。这不是调优建议,而是功能要求。冻结沙箱时,系统会将空闲沙箱的内存移出到 swap。gVisor 将沙箱内存保存为共享内存,而内核在默认 swappiness 下不会交换共享内存。项目测试表明,默认值下回收的内存为 0 字节,而设置为 100 时可回收 99.5 percent。请检查内核实际使用的值,因为某些云镜像会将 vm.swappiness = 0 写入一个您不会想到要读取的文件。
sysctl vm.swappiness
swapon --showsysctl vm.swappiness 应输出 vm.swappiness = 100,而 swapon --show 应列出一个 swapfile。如果 swappiness 输出为 0,每次冻结都会变成空操作,您仍需为每个空闲沙箱支付完整的内存成本。
在 Ubuntu 上安装 Dormice
官方记录的安装方式是将一条管道传给 bash:
curl -fsSL https://raw.githubusercontent.com/BitMiracle-AI/Dormice/main/deploy/install.sh | bash运行前先下载并阅读该脚本。该脚本以 root 身份运行,并会修改主机环境:如果尚未安装 Docker,则安装 Docker;下载 gVisor 和 Caddy 并校验校验和;创建 swapfile;写入 systemd 单元;添加防火墙规则。
curl -fsSL https://raw.githubusercontent.com/BitMiracle-AI/Dormice/main/deploy/install.sh -o dormice-install.sh
less dormice-install.sh
sudo bash dormice-install.sh --swap-gb 8--swap-gb 设置 swapfile 大小,默认值为 16。对于小型 VPS,这会占用过多磁盘空间。--mirror cn 会将下载地址切换为中国大陆可访问的镜像。重新运行安装程序会升级代码并修复配置偏差,但不会轮换 API token。
代码位于 /opt/dormice,配置位于 /etc/dormice/env,沙箱数据位于 /var/lib/dormice,dormice 和 dor 命令位于 /usr/local/bin。安装程序会在安装过程中生成 API token,并以 mode 600 写入 /etc/dormice/env。
目前没有可用于安装的带标签版本。截至 4 August 2026,该仓库没有 git 标签,也没有 GitHub releases。因此,安装程序会克隆 main,您获得的是当天早些时候提交的代码。要固定版本,必须记录实际安装的 commit。
git -C /opt/dormice rev-parse HEAD将该 hash 与部署记录一起保存。升级导致问题时,这是唯一的回退依据,因为没有可供指定的版本号。
安装程序最后会运行 dor doctor。这是一个只读主机检查工具,会启动真实的 gVisor 容器来验证运行时是否正常,而不是仅根据软件包列表判断。daemon 行为异常时,请再次运行该检查。
sudo dor doctor
systemctl is-active dormicesystemctl is-active dormice 应输出 active。如果输出 failed,原因保存在 journalctl -u dormice -n 50 中。启动失败通常是 swap 或 gVisor 依赖项的问题,而不是 daemon 本身的问题。
安装程序还会在主机上安装 Caddy。因此,在确认防火墙配置完成前,先检查当前有哪些服务正在监听。
sudo ss -lntpdaemon 绑定到 127.0.0.1:3676,并且按设计没有可更改该设置的选项。从笔记本电脑访问它需要有意进行配置,最简单的方式是使用 SSH 隧道。
ssh -L 3676:127.0.0.1:3676 root@your-server隧道建立后,您笔记本电脑上的 http://127.0.0.1:3676/console 就是 Web 控制台。首次使用 token 登录后,token 会转换为 httpOnly 会话 cookie,因此页面无法读取 token 本身。控制台中的 Connect 页面会输出已指向您自己的端点、可直接复制粘贴使用的客户端代码片段。
创建沙箱并在其中执行代码
一个操作用于创建沙箱:acquire。它具有幂等性,因此相同的密钥始终返回相同的沙箱,并会根据需要创建、唤醒、启动或恢复该沙箱。对于从未见过某个密钥的其他所有动词,服务都会返回 404。dor CLI 没有 acquire 动词,因此您的第一个沙箱需要通过控制台或客户端库创建。
通过控制台操作最快。通过隧道打开 /console,并创建名为 my-agent 的沙箱。随后即可使用 CLI 操作它。
sudo grep DORMICE_API_TOKEN /etc/dormice/env
export DORMICE_ENDPOINT=http://127.0.0.1:3676
export DORMICE_API_TOKEN=paste-the-value-here
dor sandbox ls
dor sandbox exec my-agent 'python3 --version'dor sandbox ls 会列出每个沙箱及其生命周期状态,您可以据此监控沙箱从 active 变为 frozen。dor sandbox exec 会输出 Python 3.12 版本,因为默认镜像是 Ubuntu 24.04,并且已预装 Python 3.12、Node 24、git 和 ripgrep。如果出现身份验证错误,说明您复制的令牌行包含了变量名。
使用 dor sandbox push my-agent ./script.py 传输文件,文件会保存到 /home/user/script.py;使用 dor sandbox pull my-agent notes.txt 可将文件取回。本地文件动词将单个文件大小限制为 16 MiB,而 E2B 文件接口支持流式传输,因此沙箱磁盘配额是唯一限制。
destroy 是唯一会丢失数据的动词。这也体现了该项目文档存在版本差异:主 README 和随附的 agent skill 都记录了 dor sandbox destroy <key>,而 CLI 软件包 README 记录的是 dor sandbox release <key>。请在您自己的构建中运行 dor sandbox --help,并以其输出为准。
让现有的 E2B 代码连接到您自己的服务器
这就是需要关注它的原因。npm 官方的 e2b 软件包未经修改即可与 Dormice 通信。在 SSH 隧道已打开的情况下,从您的笔记本电脑运行以下命令,这样服务器上不会有新的监听服务。
npm init -y
npm i e2b tsximport { Sandbox } from 'e2b';
const sbx = await Sandbox.create({
apiKey: `e2b_${process.env.DORMICE_API_TOKEN}`,
apiUrl: 'http://127.0.0.1:3676/e2b/api',
sandboxUrl: 'http://127.0.0.1:3676/e2b/envd',
});
const result = await sbx.commands.run('python3 -c "print(6 * 7)"');
console.log(result.exitCode, result.stdout);
await sbx.kill();DORMICE_API_TOKEN=paste-the-value-here npx tsx index.ts正常运行时会输出退出代码 0 和 42。API 密钥是您的 Dormice 令牌,前面加上 e2b_ 前缀。这是兼容层要求的格式。
该兼容层不是存根实现。项目的端到端测试套件会通过官方软件包,针对真实的 Docker 和 gVisor daemon,测试流式 stdout 和 stderr、后台命令、交互式 PTY、签名的上传和下载 URL、目录监控以及端口代理。在迁移任何实际业务前,需要注意以下差异:
- 尚未实现模板构建。模板是您自行构建并通过
dor template add注册的 Docker 镜像,Sandbox.create('name')会解析该镜像。未注册的名称会返回 404,而不是模拟成功。 - 通过 E2B 接口创建的沙盒会获得实际的截止时间,因为 E2B 语义要求这样做。通过原生 API 创建的沙盒永远不会被强制设置截止时间。
- 冻结的沙盒会保留其中的进程,并从暂停位置继续运行。因此,这里的暂停和恢复并不是您可能熟悉的停止后冷启动。
沙箱可以阻止什么,以及不能阻止什么
gVisor 在用户空间拦截容器的系统调用并自行处理,因此沙箱中的代码不会直接与主机内核通信。在沙箱内部,所有内容都以非特权用户 uid 1000 运行。这种组合可以处理常见情况:生成的脚本运行 rm -rf /、占满磁盘,或不断创建进程直到某个进程崩溃时,只会破坏自身的沙箱,影响不会扩散。
以下情况无法由它阻止。这些属于您的职责。
- 沙箱可以正常访问出站网络。生成的代码可以下载任意内容,也可以发送它找到的任意内容。安装程序的网络加固只处理两项内容:阻止容器流量访问云元数据服务 169.254.0.0/16。云平台会通过该服务向所有能够访问它的对象提供实例凭据;并在 Docker 的
daemon.json中使用"icc": false关闭容器之间的流量。其他流量均未被阻止。阅读sudo iptables -S DOCKER-USER,为沙箱不应访问的私有地址范围添加您自己的 DROP 规则。 - Docker 会在防火墙规则之前插入自己的规则。因此,即使 ufw 认为端口已关闭,已发布的容器端口仍可能响应来自互联网的请求。在此主机上暴露任何内容前,请阅读Docker 如何绕过 ufw 发布端口和VPS 的 ufw 防火墙基础。
- gVisor 是用户空间内核,不是虚拟机监控程序。这是有意的取舍,因为冻结功能要求沙箱以进程运行,而要求 KVM 会导致该组件无法在所有环境中安装。如果您的威胁模型要求硬件虚拟化,请使用 Firecracker 类的隔离方案,并接受由此带来的运维成本。
- API 令牌是客户端侧的整个安全边界。任何持有
DORMICE_API_TOKEN的对象都可以在该计算机上创建、读取和销毁所有沙箱。为代理进程分配专用的VPS 最小权限用户,并按照保护 SSH 密钥的方式保护该令牌。在 VPS 上安全运行 Claude Code中的做法同样适用。
守护进程本身以 root 身份运行在您的主机上。gVisor 可以保护主机免受沙箱内代码的影响,但无法保护主机免受守护进程或令牌持有者的影响。因此,运行 Dormice 的计算机应只用于此用途。如果您的代理还通过 MCP(模型上下文协议)访问工具,请出于同样的原因将这些MCP 服务器部署在单独的 VPS 上。
4 GB 和 8 GB 能容纳多少个沙箱?
有两项会占用内存:主机自身的基础开销,以及当前处于唤醒状态的每个沙箱的工作集。为 Ubuntu、Docker 和 daemon 预留约 1 GB,然后用剩余内存除以一个沙箱实际使用的内存。运行读取少量文件的 Python 脚本时,一个沙箱通常占用约 200 到 300 MiB。运行编译器或完整测试套件时,一个沙箱可能超过 1 gibibyte。
The data behind this chart
[
{
"host": "4 GB VPS",
"active_at_512_mib": 6,
"active_at_1_gib": 3,
"frozen_on_16gb_swap": 16
},
{
"host": "8 GB VPS",
"active_at_512_mib": 14,
"active_at_1_gib": 7,
"frozen_on_16gb_swap": 16
}
]如果每个沙箱使用 512 MiB,4 GB VPS 可同时唤醒约 6 个沙箱;如果每个沙箱使用完整的 1 gibibyte,则约为 3 个。8 GB VPS 对应的数量为 14 和 7。这些数值是并发工作的上限,只是算术结果,不是基准测试结果,因此应在自身负载运行期间监控 free -m。
冻结的沙箱受 swap 限制,而不是受 RAM 限制,这正是该设计的目的。一个曾占用 1 gibibyte 的冻结沙箱会在 swap 中保留大致相同的数据,但几乎不占用常驻内存,因此安装程序默认创建的 16 GB swapfile 大约可以容纳 16 个冻结沙箱。超过这个数量后,它们需要进入已停止状态,此时只占用磁盘空间。磁盘才是这里长期运行的实际限制:每个沙箱都会保留自己的文件系统,几十个各自包含 node_modules 目录的 agent 会在内存问题变得突出之前,先填满小容量卷。
冻结、停止、归档:生命周期参数
默认情况下,沙箱空闲 10 分钟后冻结,停止 3 天后归档;前提是已配置归档。将 stopAfterSeconds 设置为 null 可获得常驻代理:它可能在空闲时冻结,但不会冷启动。
归档是可选功能,守护进程会明确报告配置状态。设置 4 个 DORMICE_S3_* 变量后,停止的沙箱磁盘会使用 tar 和 zstd 打包,上传到任意兼容 S3 的存储桶,并释放本地空间。该存储桶可以是您在另一台机器上自行托管的 MinIO 存储桶。如果不设置这些变量,沙箱会永久保持停止状态;要求归档的策略会被拒绝,而不是被静默忽略。恢复过程也会明确显示,而不是在后台静默进行:下一次 acquire 会立即返回 restoring 状态和进度值;磁盘恢复后,状态会切换为 ready。
是否现在就可以依赖它?
直说:不要将它用于任何无法重建的内容。仓库中的第一个提交日期是 8 July 2026。截至 4 August 2026,它有 446 个 stars、37 个 forks,采用 Apache-2.0 许可证,而且完全没有 tagged release。README 自己的状态说明也表示,目前没有任何内容可用于生产环境。
这种组合带来了明确的风险。由于安装程序会跟踪 main,代码可能随时发生变化。接口仍在调整,这正是同一仓库的两个文件中使用了两个不同删除动词的原因。一个仅有四周历史的项目也可能直接停止维护,因为许可证条款没有要求任何人继续开发。
E2B 兼容性使这种风险仍然可控。应用通过一个背后有托管实现的协议进行通信,因此如果 Dormice 停止维护,只需修改两个 URL 即可继续工作。让 agent 面向 E2B 接口而不是原生 API 编写,就能保留这条退出路径。原生 @dormice/sdk package 目前也尚未发布到 npm,因此使用它需要从仓库构建,这是应先采用兼容路径的另一个原因。
请在可以承受数据丢失的环境中运行它。使用脚本重建主机,将 token 排除在所有提示和提交之外,并按照自己的备份计划,将任何需要保留的内容从 sandbox 中导出。
FAQ
Dormice 可以用于生产环境吗?
不可以,项目本身也明确说明了这一点。README 的状态行指出,目前其中没有任何内容可用于生产环境。截至 4 August 2026,该仓库创建约四周,没有 git 标签,也没有发布版本,因此没有可固定的版本号。安装程序会克隆 main 分支,这意味着每次运行都会获取最新提交。每次安装后记录 git -C /opt/dormice rev-parse HEAD,并将所有重要数据保存在沙箱之外。
Dormice 与为代理提供一个一次性 VM 有什么区别?
一次性 VM 是一台带 SSH 的机器。您为某个会话创建它,使用完后删除。Dormice 是一个执行 API:您的程序调用 acquire,然后调用 exec,获取标准输出和退出码,中间不会建立 shell 会话。VM 适合需要暂时使用完整计算机的人或代理。Dormice 适合每天多次运行生成代码、且不希望每次运行都承担整台机器的配置和销毁开销的应用程序。
官方 E2B SDK 真的无需修改代码即可工作吗?
可以,但需要修改配置。将 apiUrl 和 sandboxUrl 指向 daemon 上的 /e2b/api 和 /e2b/envd,并在 API key 中使用带有 e2b_ 前缀的 Dormice token。命令执行、PTY 会话、文件传输、签名 URL 和端口代理都已由项目通过官方软件包运行的端到端测试覆盖。模板构建是主要缺口:e2b template build 尚未实现,因此模板就是您构建并通过 dor template add 注册的 docker 镜像。
4 GB VPS 可以运行多少个沙箱?
如果每个沙箱使用 512 MiB,同时保持唤醒状态的沙箱数量约为 6;如果每个沙箱使用完整的 1 GiB,则约为 3。这里已为操作系统、Docker 和 daemon 预留约 1 GB。冻结沙箱受 swap 容量限制,因此安装程序默认创建的 16 GB swapfile 可以容纳约 16 个曾使用 1 GiB 内存的沙箱。请在实际负载下使用 free -m 测量您自己的环境,因为运行测试套件的沙箱所使用的资源,可能是运行小型脚本的沙箱的数倍。
为什么 Dormice 要求将 vm.swappiness 设置为 100?
冻结沙箱意味着将其空闲内存移出到 swap。gVisor 将沙箱内存作为共享内存保存,而 Linux 内核在默认 swappiness 下不会交换共享内存。因此,在默认值下,冻结操作不会回收任何内存,沙箱仍会持续占用全部内存。项目测得默认值下回收的内存为 0 bytes,而设置为 100 时可回收 99.5 percent。请使用 sysctl vm.swappiness 检查生效值,不要只读取配置文件,因为某些云镜像将该值设置为 0。