起因
我想在一台 Ubuntu 机器上跑起 E2B 的整套虚拟化服务。主要其实是服务于内部研发的场景:coding agent server、sandbox 执行、数据分析容器(类似于一个远程 Jupyter Kernel)。未来当然是有可能开放到业务服务中的,比如说在 Agent 中执行用户的脚本之类的。
为啥要自己做呢,因为运维和基础中间件不给做。问我要业务场景,我有个锤子的业务场景需要?现在还有方案试图通过 K8S 给每个用户起一个 Pod 来处理 Agent 需要用户隔离的场景呢,要不然就是通过目录(也是一绝)。这种东西用途太广泛了,并且已经是业界的成熟场景解决方案,不太明白为什么还需要一个“业务需求”才能推动。牢骚满腹不如自己开干!
和同事在他的台式机上最终跑通了这个 self-host 链路:
Windows 本地 -> 10.10.21.21:3000 -> E2B API -> orchestrator -> Firecracker microVM
最后我在自己的 Windows 开发机里成功创建了 sandbox:
{
"alias": "base",
"clientID": "6532622b",
"envdVersion": "0.6.6",
"sandboxID": "ixwcnaf4mnrytcuys54lf",
"templateID": "gg1xl4lhgm0cht8rjcn9"
}
这篇记录按真实排查时间线整理,重点放在部署过程中的误判、和比较容易发生的坑。
先确认环境
E2B 的核心不是普通 Docker 服务,而是基于 Firecracker/KVM 的 microVM sandbox。因此第一步不是安装软件,而是确认宿主机是否真的具备虚拟化能力。Ubuntu 上先跑了这些检查:
uname -a
lsb_release -a || cat /etc/os-release
egrep -c '(vmx|svm)' /proc/cpuinfo
lscpu | egrep 'Model name|Virtualization|Hypervisor|CPU\(s\)'
ls -l /dev/kvm /dev/net/tun
kvm-ok 2>/dev/null || true
mount | grep cgroup
stat -fc %T /sys/fs/cgroup
free -h
df -h
lsmod | egrep 'kvm|vhost|tun'
docker --version
docker buildx version
这台机器的条件比较理想:
Ubuntu 26.04 LTS
Intel i7-10700
VT-x enabled
/dev/kvm exists
/dev/net/tun exists
cgroup2
30Gi memory
817G free disk
Docker 29.5.2
这一步很重要。 /dev/kvm 不存在的话宿主机本身就不具备跑 Firecracker 后端的条件。
Docker Hub 的镜像坑
Docker 本身装好了,但 hello-world 拉不下来:
failed to fetch anonymous token:
Get "https://auth.docker.io/token?...": i/o timeout
我分别测了几个地址:
curl -I --connect-timeout 10 https://registry-1.docker.io/v2/
curl -I --connect-timeout 10 https://auth.docker.io/token
curl -I --connect-timeout 10 https://github.com
现象是:
registry-1.docker.io可访问github.com可访问auth.docker.io超时
也就是说,Docker Hub 的 registry 本身可达,但拿 token 的服务不可达。Docker 拉镜像必须先走 token 授权,所以匿名拉取全部失败。
我试了网上一些 Docker mirror,很多已经返回 410 Gone 或者不是标准 registry 响应。最后实测 dockerproxy.net 可用,配置 Docker mirror:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
"registry-mirrors": [
"https://dockerproxy.net"
]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
然后重新验证:
docker pull hello-world
docker run --rm hello-world
同时我把 IPv4 forwarding 打开,后面 sandbox 网络会用到:
sudo sysctl -w net.ipv4.ip_forward=1
sudo tee /etc/sysctl.d/99-e2b.conf >/dev/null <<'EOF'
net.ipv4.ip_forward=1
EOF
安装基础工具链
E2B infra 文档要求的基础工具包括:
- Terraform 1.7.5
- Packer
- Go
- Docker Buildx
- Node/NPM
- gsutil
Terraform 和 Packer 按指定版本安装:
mkdir -p ~/bin ~/src
cd /tmp
curl -fsSLO https://releases.hashicorp.com/terraform/1.7.5/terraform_1.7.5_linux_amd64.zip
unzip -o terraform_1.7.5_linux_amd64.zip
install -m 0755 terraform ~/bin/terraform
curl -fsSLO https://releases.hashicorp.com/packer/1.11.2/packer_1.11.2_linux_amd64.zip
unzip -o packer_1.11.2_linux_amd64.zip
install -m 0755 packer ~/bin/packer
grep -qxF 'export PATH="$HOME/bin:$PATH"' ~/.zshrc || echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc
export PATH="$HOME/bin:$PATH"
terraform version
packer version
基础依赖:
sudo apt update
sudo apt install -y \
golang-go nodejs npm git jq make build-essential pkg-config \
qemu-utils bridge-utils iptables nftables \
postgresql-client redis-tools unzip curl wget
E2B 官方 self-host 和单机 Ubuntu 的差异
拉取仓库:
cd ~/src
git clone https://github.com/e2b-dev/infra.git e2b-infra
cd e2b-infra
主要的部署依赖、脚本、产物都在 Makefile 和 IaC 目录里,仓库主要提供的是:
iac/provider-aws
iac/provider-gcp
没有现成的 provider-linux。这意味着:完整 self-host 生产路径主要是 AWS/GCP。单台 Ubuntu 不是官方的一键生产部署路径。不过仓库里有基本的部署依赖和信息,可以自己手动搞:
DEV-LOCAL.md
packages/local-dev
- Docker Compose 跑 Postgres、Redis、ClickHouse、Grafana、Loki 等
- 本机 root 跑 orchestrator
- Firecracker 直接使用
/dev/kvm - API 和 client proxy 本机启动
这不是官方生产部署的替代品,但很适合做自托管实验、agent sandbox 原型和内部 PoC。
Go toolchain 自动下载也会卡
进入 e2b-infra 后执行 go version,Go 会自动下载仓库要求的 Go 1.26.3:
go: downloading go1.26.3
Get "https://proxy.golang.org/...": i/o timeout
我一开始在仓库目录里执行:
go env -w GOPROXY=https://goproxy.cn,direct
结果也会卡住。排查了一下,发现原因是 Go 先读取当前仓库的 go.work/go.mod,发现需要新 toolchain,于是还没来得及写代理配置,就先去下载 toolchain 了。
解决方式是跳出仓库,并临时禁用自动 toolchain:
cd ~
GOTOOLCHAIN=local go env -w GOPROXY=https://goproxy.cn,direct
GOTOOLCHAIN=local go env -w GOSUMDB=sum.golang.google.cn
GOTOOLCHAIN=local go env -w GOTOOLCHAIN=auto
GOTOOLCHAIN=local go env GOPROXY GOSUMDB GOTOOLCHAIN GOVERSION
再回仓库:
cd ~/src/e2b-infra
go version
准备 Firecracker 本机环境
根据 DEV-LOCAL.md,本机需要 NBD 和 hugepages:
cd ~/src/e2b-infra
sudo modprobe nbd nbds_max=64
lsmod | grep nbd
sudo sysctl -w vm.nr_hugepages=2048
grep HugePages /proc/meminfo
echo 'nbd nbds_max=64' | sudo tee /etc/modules-load.d/nbd.conf
sudo tee /etc/sysctl.d/99-e2b-local.conf >/dev/null <<'EOF'
vm.nr_hugepages=2048
net.ipv4.ip_forward=1
EOF
sudo sysctl --system | egrep 'hugepages|ip_forward'
后面我也补了 userfaultfd:
echo 1 | sudo tee /proc/sys/vm/unprivileged_userfaultfd
sudo tee /etc/sysctl.d/98-e2b-userfaultfd.conf >/dev/null <<'EOF'
vm.unprivileged_userfaultfd=1
EOF
下载 E2B 预构建内核和 Firecracker
安装 google-cloud-cli / gsutil 后,下载 E2B 的公开构建产物:
cd ~/src/e2b-infra
make download-public-kernels
make download-public-firecrackers
ls -lah packages/fc-kernels/ | head
find packages/fc-versions/builds -maxdepth 4 -name firecracker -type f -exec ls -lah {} \; | head
能看到类似:
packages/fc-kernels/vmlinux-6.1.158
packages/fc-versions/builds/v1.14.1.../amd64/firecracker
这一步如果缺失,后面 orchestrator 就没有可用的 kernel/firecracker 二进制。
启动 local infra 时继续被镜像拉取绊住
启动 local infra:
cd ~/src/e2b-infra
make local-infra
第一次失败在镜像拉取:
postgres:17.4
python:3.12-alpine
error from registry
dockerproxy.net ... 500 Internal Server Error
我先提取 Docker Compose 需要的镜像:
docker compose -f packages/local-dev/docker-compose.yaml config --images | sort -u
再逐个从可用 mirror 拉取并 tag 回原名。最终 local infra 成功启动:
docker compose -f packages/local-dev/docker-compose.yaml ps
主要服务包括:
postgres:17.4
redis:7.4.2
clickhouse:25.4.5.24
grafana/grafana:12.0.0
grafana/loki:3.4.1
grafana/tempo:2.8.2
otel/opentelemetry-collector-contrib
随后执行迁移和 seed:
make -C packages/db migrate-local
make -C packages/clickhouse migrate-local
make -C packages/envd build
make -C packages/local-dev seed-database
busybox 缺失会被包装成 internal error
构建 base template 时,最初只看到一个很笼统的错误:
BuildError: An internal error occurred.
真正原因在 orchestrator 日志里:
error reading busybox file
packages/orchestrator/.busybox/1.36.1/amd64/busybox:
no such file or directory
这里需要补一下 busybox 这个依赖:
cd ~/src/e2b-infra
make -C packages/orchestrator fetch-busybox BUILD_ARCH=amd64
如果 target 不可用,也可以手动下载:
mkdir -p packages/orchestrator/.busybox/1.36.1/amd64
curl -fL --connect-timeout 20 \
-o packages/orchestrator/.busybox/1.36.1/amd64/busybox \
https://github.com/e2b-dev/fc-busybox/releases/download/v1.36.1/busybox_v1.36.1_amd64
chmod +x packages/orchestrator/.busybox/1.36.1/amd64/busybox
SDK/脚本侧的错误太抽象和笼统了,必须看 orchestrator 的日志。
启动三大前台服务
local infra 启动后,还需要分别启动三个前台服务。
终端 A:orchestrator
cd ~/src/e2b-infra
sudo env "PATH=$PATH" make -C packages/orchestrator run-local 2>&1 | tee /tmp/e2b-orchestrator.log
健康检查:
curl -s http://localhost:5008/health && echo
终端 B:API
cd ~/src/e2b-infra
make -C packages/api run-local 2>&1 | tee /tmp/e2b-api.log
健康检查:
curl -s http://localhost:3000/health && echo
终端 C:client proxy
cd ~/src/e2b-infra
make -C packages/client-proxy run-local 2>&1 | tee /tmp/e2b-client-proxy.log
健康检查:
curl -s http://localhost:3003/health && echo
旧 orchestrator 进程占端口
调试时多次重启 orchestrator,容易遇到端口占用:
listen tcp :5007: bind: address already in use
listen tcp :5012: bind: address already in use
listen tcp 0.0.0.0:5016: bind: address already in use
先查占用:
sudo ss -lntp | egrep ':(5007|5008|5012|5016|5017|5018)\b'
找到占用这些端口的 orchestrator 进程后,清理旧进程:
sudo kill -9 <orchestrator_pid>
也可以自动提取并清理:
PIDS=$(sudo ss -H -lntp \
| grep -E ':(5007|5008|5012|5016|5017|5018)[[:space:]]' \
| sed -n 's/.*pid=\([0-9]\+\).*/\1/p' \
| sort -u)
for p in $PIDS; do
sudo kill -9 "$p"
done
最后确认端口清空:
sudo ss -lntp | egrep ':(5007|5008|5012|5016|5017|5018)\b' || true
base template 第一次构建非常慢
执行:
cd ~/src/e2b-infra
make -C packages/shared/scripts local-build-base-template
过程中会看到:
FROM e2bdev/base
Base Docker image size: 468 MB
Setting up system files
Creating file system and pulling Docker image
Uncompressing layer ...
mke2fs 1.47.2
Creating filesystem with 6400000 4k blocks
这里创建的是一个大约 24 GiB 的 ext4 rootfs:
6400000 * 4 KiB ~= 24.4 GiB
这一步非常容易误判为卡死。我这次第一次构建最终耗时:
Build finished, took 40m26s
判断它是不是还活着,可以看进程的 CPU 使用等信息。这一步成功之后,后面重启服务不需要反复构建 base template。
创建 sandbox 成功,但默认只活 15 秒
第一次创建:
curl -s -X POST http://localhost:3000/sandboxes \
-H "X-API-Key: <LOCAL_DEV_API_KEY>" \
-H "Content-Type: application/json" \
-d '{"templateID": "base"}' | jq
返回成功:
{
"alias": "base",
"clientID": "6532622b",
"envdVersion": "0.6.6",
"sandboxID": "iuumrzrekf7n0vg9afhll",
"templateID": "gg1xl4lhgm0cht8rjcn9"
}
但随后查询发现 sandbox 已经不存在。我一开始以为是 Firecracker 启动失败,后来查日志发现不是。
orchestrator 显示 sandbox 已经成功加入 map:
adding sandbox to map
sandbox.ip: 10.11.0.67
sandbox.kernel.version: vmlinux-6.1.158
sandbox.firecracker.version: v1.14.1...
实际上在 API 日志里就有具体原因,sandbox 的有效生命周期就 15s,到点后正常 kill 掉了。
start_time: 2026-06-23T17:12:56
end_time: 2026-06-23T17:13:11
orchestrator 进程的日志有这些关键信息:
Removing sandbox
state_action: kill
解决方式是创建时传 timeout:
curl -s -X POST http://localhost:3000/sandboxes \
-H "X-API-Key: <LOCAL_DEV_API_KEY>" \
-H "Content-Type: application/json" \
-d '{"templateID":"base","timeout":300}' | jq
Python SDK 调用方式
本地或服务器上都可以这样用:
from e2b import Sandbox
sbx = Sandbox(
api_key="<LOCAL_DEV_API_KEY>",
api_url="http://10.10.21.21:3000",
sandbox_url="http://10.10.21.21:3002",
template="base",
timeout=3600,
)
print(sbx.id)
注意:
api_url是3000sandbox_url是30023003是 client-proxy health- 创建时要显式传
timeout
重启后的启动顺序
机器重启后,不需要重新构建 base template。启动顺序是:
1. Docker local infra
cd ~/src/e2b-infra
docker compose -f packages/local-dev/docker-compose.yaml up -d
docker compose -f packages/local-dev/docker-compose.yaml ps
2. orchestrator
cd ~/src/e2b-infra
sudo env "PATH=$PATH" make -C packages/orchestrator run-local 2>&1 | tee /tmp/e2b-orchestrator.log
3. API
cd ~/src/e2b-infra
make -C packages/api run-local 2>&1 | tee /tmp/e2b-api.log
4. client proxy
cd ~/src/e2b-infra
make -C packages/client-proxy run-local 2>&1 | tee /tmp/e2b-client-proxy.log
可以通过一个脚本一键拉起。
Linux 单机 provider(开发中
E2B 官方的 infra 仓库给出的 provider 主要是面向云服务厂商的,并没有真的 self-host Linux 集群 provider。我把上述的操作整合成了一个 E2B Linux 单机 provider repo,有兴趣的朋友可以试试看,有 bug 欢迎提 issue 我来修,希望最后可以升级成一个 K8S provider。
总结
迷迷茫茫整了一通,感谢我 codex 爹最终部署好了。下次让 Pi ssh 上去自己搞得了……