自托管 Dev Sandbox:用 Docker 和 Go 搭一套带预览链接的开发环境

"sandboxed README 说明了 Go 控制平面、Docker、Traefik、SQLite、preview URL、idle stop 和生产硬化边界。"
"Docker 官方资源限制文档说明容器默认没有 CPU 和内存限制,需要显式配置约束。"
"Docker Sandboxes 官方文档把 microVM、独立 Docker daemon、网络代理和凭据隔离作为更强安全模型。"
"Traefik Docker provider 可通过 Docker labels 获取动态路由配置,用 Host rule 路由到容器服务。"
给每个 PR 开独立预览环境,传统做法是托管到 Vercel 或 Netlify。但如果成本敏感、数据要走私有网络、或者想把基础设施握在自己手里,单机 Docker + Go 控制平面可以替代托管平台。每个 sandbox 有独立 preview URL,资源有上限,安全有边界——不用 K8s,不用多节点。
判断表 — 何时用什么方案
拿到”自托管预览环境”需求,很多人会先想到 shell script 批量 docker run,或者直接上 K8s。先看一张判断表:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 内部团队 <10 人并发预览,信任边界在团队内 | 单机 Docker + Go 控制平面 | 资源密度可控,架构简单,不用运维 K8s 集群 |
| 内部团队 >20 人并发,或需要多节点高可用 | K8s + Namespace 隔离 | 单机扛不住,需要跨节点调度和滚动升级 |
| 外部用户或不可信代码执行(autonomous agent) | microVM(Docker Sandboxes / Firecracker) | Docker socket = 主机 root 权限,不能和不可信业务混跑 |
| 简单静态预览,无持久化需求 | shell script + 随机端口 | 能跑,但 preview URL 不稳定,没有资源限制和安全边界 |
判断标准:
团队规模:单机适合 10 人以内并发预览。估算公式:每个 sandbox 预留 512MB RAM + 0.5 CPU,16GB 内存的主机最多扛 20 个 sandbox。超过这个密度,需要 K8s 调度或 microVM 拆节点。
信任边界:内部团队成员、受信任用户可以跑在 Docker 容器里。但如果要给外部陌生人或 autonomous agent 执行任意代码,Docker socket 方案不安全。Docker 官方的 Sandboxes 用 microVM 隔离,每个 sandbox 有自己的 Docker daemon、文件系统和网络,适合不信任 host 环境的场景。
升级路径:先从单机 Docker 起步,验证需求和资源密度。如果并发需求超过单机容量,或需要跨节点高可用,再升级到 K8s。如果信任边界从”内部团队”变成”外部用户”,升级到 microVM。
tastyeffectco/sandboxes 的 README 明确说明:单机 Docker 方案适合 AI app-builder、agent platform 和 coding playground。它不是 microVM 级别隔离,而是用 Go 控制平面在单机 Docker 上创建容器并暴露 preview URL。
架构拆解 — 控制平面核心组件
单机 Docker 预览环境的核心不是 docker run 一把梭,而是需要一个控制平面管理生命周期、路由注册和资源回收。tastyeffectco/sandboxes 的架构拆成 6 个模块:
Go 控制平面(sandboxd):运行在容器里,挂载 host 的 Docker socket 和数据目录。它通过 Docker CLI 管理 sandbox 容器的生命周期:创建、启动、停止、删除。所有 sandbox 的元数据存到 SQLite,作为 source of truth。
Docker socket 挂载:控制平面对 Docker daemon 的访问入口。sandboxd 通过挂载 /var/run/docker.sock 获得创建和管理容器的权限。这是整个架构的权限边界:挂了 Docker socket,控制平面对主机有很高权限。
Traefik labels 注册:每个 sandbox 容器启动时,控制平面通过 Docker labels 注入 Traefik 路由配置。Traefik 作为反向代理,从 labels 发现路由规则,把 *.preview.example.com 的请求转发到对应容器。
SQLite 存储元数据:每个 sandbox 有一个唯一 ID 和对应目录,元数据存到 SQLite。工作区在 SANDBOXED_DATA_DIR/workspaces/ 下,每个 sandbox 一个子目录,存放源码、配置和产物。
idle reaper 和 pressure reaper:idle reaper 检查 sandbox 容器的空闲时间,超过阈值就停止容器释放 RAM。pressure reaper 监控主机内存压力,当内存紧张时停止部分 sandbox,防止主机 OOM。这两个 reaper 是资源回收的核心机制。
wake path:sandbox 容器被 idle reaper 停止后,首次访问 preview URL 时需要唤醒。Traefik catch-all 把请求转发到控制平面,控制平面启动容器并返回 warming page,等待容器就绪后再把请求转发过去。
这套架构的最小可行版本:Go 控制平面容器 + Docker socket 挂载 + Traefik + SQLite + idle reaper。本地 quick start 需要 Docker Engine 和 Compose plugin。
Preview URL 实现
preview URL 的关键是稳定域名,不是随机端口。每个 sandbox 有独立的 {sandbox_id}.preview.example.com 域名。
Traefik Docker provider 配置
Traefik 通过 Docker provider 从容器 labels 发现路由配置。配置示例:
# traefik.yml
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
exposedByDefault: false 表示只有显式配置 labels 的容器才会被 Traefik 发现。
Host rule 和 Docker labels
控制平面在创建 sandbox 容器时注入 labels。示例:
labels:
- "traefik.enable=true"
- "traefik.http.routers.sandbox123.rule=Host(`sandbox123.preview.example.com`)"
- "traefik.http.routers.sandbox123.entrypoints=websecure"
- "traefik.http.services.sandbox123.loadbalancer.server.port=3000"
Host rule 把 sandbox123.preview.example.com 的请求路由到该容器。loadbalancer.server.port 指定容器内部应用监听的端口。
wake-on-request 路径
sandbox 容器被 idle reaper 停止后,首次访问 preview URL 会触发 wake 流程:
- DNS 解析到 Traefik(需要 wildcard DNS 配置
*.preview.example.com) - Traefik 发现该 sandbox 的路由规则,但容器已停止
- Traefik catch-all 把请求转发到控制平面的 wake handler
- 控制平面启动 sandbox 容器,返回 warming page
- 容器就绪后,Traefik 把后续请求直接转发到容器
catch-all 的关键是配置一个 fallback router,优先级低于所有 sandbox 路由:
labels:
- "traefik.http.routers.catch-all.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.preview.example.com`)"
- "traefik.http.routers.catch-all.priority=1"
- "traefik.http.routers.catch-all.service=wake-service"
所有未被 sandbox 路由匹配的请求会落到 catch-all,由控制平面处理 wake 逻辑。
安全边界 — Docker socket 权限
挂载 Docker socket 等于把主机 root 权限给了控制平面。这是整个架构的安全基线:适合内部团队和受信任用户,不适合不可信代码执行。
Docker socket 的风险
Docker 官方文档明确说明:daemon 有攻击面,如果不安全地开放 Docker API,远程非 root 用户可能获得主机 root 访问。挂载 /var/run/docker.sock 的容器可以通过 Docker CLI 创建、修改和删除容器,也能访问主机的文件系统和网络。
这意味着:
- 控制平面容器对主机有很高权限
- 不能把控制平面和不可信业务混跑在同一主机
- sandbox 容器里跑的代码仍然能通过控制平面间接影响主机
不信任场景边界
单机 Docker + Go 控制平面适合的场景:
- 内部团队成员的预览环境
- 受信任用户的 coding playground
- AI app-builder 或 agent platform 的内部验证环境
不适合的场景:
- 外部陌生人的任意代码执行
- autonomous agent 的生产执行环境(不信任 host)
- 需要强隔离的多租户平台
如果信任边界从”内部团队”变成”外部用户”,应该升级到 Docker 官方 Sandboxes 或 Firecracker microVM。Docker Sandboxes 的安全模型包含:hypervisor 隔离、独立网络、独立 Docker daemon、独立文件系统和 credential 隔离。每个 sandbox 是完整的 microVM,而不是共享 host Docker daemon 的容器。
生产硬化清单
如果要生产部署,补齐这些边界:
- 网络隔离:控制平面和 sandbox 容器跑在独立网络,不要和业务网络混用
- API 认证:本地 quick start 默认无认证,生产必须开启 token 或其他认证机制
- 最小暴露面:preview URL 通过 Traefik 反向代理暴露,不直接开放 Docker API 端口
- TLS:preview URL 需要 wildcard TLS 证书,避免明文传输
- 日志和监控:控制平面的 API 请求、容器生命周期事件需要记录和告警
如果需要更强的安全边界,参考 AI Agent 沙盒实战指南,对比 gVisor、Firecracker 和 Kubernetes 边界方案。
资源限制 — memory / CPU / PIDs
Docker 容器默认没有资源约束。如果不加限制,一个 sandbox 容器能把主机 RAM 和 CPU 吃满,影响其他 sandbox 和主机进程。多租户预览环境的底线是硬约束每个容器的资源上限。
Docker 默认行为
Docker 官方文档说明:容器可按内核调度器允许使用主机资源。没有显式配置 --memory 或 --cpus 时,容器能抢占主机空闲资源。
memory 硬限制
--memory 设置容器可用内存上限。示例:
docker run --memory="512m" --memory-swap="512m" sandbox-image
--memory-swap 设置内存+交换空间上限。如果 --memory-swap 等于 --memory,容器不使用 swap。
内存超限会触发 OOM killer,杀死容器进程。主机 OOM 时也可能影响其他容器和主机进程。
CPU 份额限制
--cpus 设置容器可用 CPU 数量。示例:
docker run --cpus="0.5" sandbox-image
容器最多使用 0.5 个 CPU 的计算能力。多 sandbox 并发时,CPU 份额限制防止一个容器抢占全部 CPU。
进程数限制
--pids-limit 防止 fork bomb。示例:
docker run --pids-limit=100 sandbox-image
容器最多创建 100 个进程。超过限制后,fork() 会失败。
Compose 配置示例
用 Docker Compose 时,在 deploy.resources.limits 里配置:
services:
sandbox:
image: sandbox-image
deploy:
resources:
limits:
cpus: "0.5"
memory: 512M
pids: 100
Compose 的 deploy.resources 在 Docker Swarm 模式下生效。单机 Docker 需要手动传 --memory、--cpus、--pids-limit 参数,或用 docker-compose --compatibility 运行。
控制平面在创建 sandbox 容器时应该注入这些限制参数,而不是依赖用户手动配置。
运维 — 镜像缓存与 Docker Hub 限流
多 sandbox 高频创建和销毁时,镜像拉取会成为瓶颈。Docker Hub 有 pull rate limit 和 abuse rate limit,具体策略随账号类型和套餐变化。生产环境不能把每个 sandbox 的镜像拉取都交给公网 Docker Hub。
Docker Hub 限流
Docker 官方文档说明:匿名用户、认证用户、团队账号有不同的 pull rate limit。超过限制后,拉取请求会被拒绝。具体数字会随政策调整,查 Docker Hub usage and limits 获取最新信息。
多 sandbox 场景的影响:
- 高频创建 sandbox 时,每个容器启动都要拉镜像
- idle reaper 停止后重新启动,又要拉镜像
- 同一镜像被多个 sandbox 重复拉取
镜像预热和缓存策略
生产环境需要这些措施:
镜像预热:控制平面启动前,预拉取常用镜像到本地。减少 sandbox 启动时的拉取等待时间。
私有 registry:把常用镜像推到私有 registry(如 Harbor、AWS ECR、GCP Artifact Registry)。控制平面从私有 registry 拉镜像,不直接访问 Docker Hub。
Docker Hub 登录:如果必须从 Docker Hub 拉镜像,用认证账号获得更高 pull rate limit。Docker 官方建议生产环境登录 Docker Hub,而不是匿名拉取。
镜像缓存:Docker daemon 本身会缓存已拉取的镜像。但如果 sandbox 容器频繁删除和重建,缓存可能被清理。控制平面应该避免频繁删除镜像层。
内网镜像加速
如果主机在内网环境,参考企业内网 Docker 拉取超时排障配置镜像加速和代理。
排障清单 — 预览 URL 不可达
preview URL 打不开时,按这 5 个步骤排查:
1. DNS 是否指向 Traefik
检查 wildcard DNS 配置。*.preview.example.com 的 A 记录或 CNAME 应该指向 Traefik 所在主机的 IP。
判定方法:用 dig 或 nslookup 查询域名。
dig sandbox123.preview.example.com
返回的 IP 应该是 Traefik 主机 IP,而不是其他地址。
2. Traefik 是否发现容器 labels
检查 Traefik Docker provider 配置和容器 labels。
判定方法:查看 Traefik dashboard 或日志。
docker logs traefik-container | grep "sandbox123"
Traefik 日志应该显示发现了 sandbox123 的路由规则。如果没有,检查:
traefik.enable=truelabel 是否存在exposedByDefault: false配置是否正确- Traefik 是否正确挂载 Docker socket
3. 容器是否启动
检查 sandbox 容器状态。
判定方法:用 docker ps 查看容器是否 running。
docker ps | grep sandbox123
如果容器已停止,可能是 idle reaper 触发了停止,或者 wake path 未触发重启。访问 preview URL 时,控制平面的 wake handler 应该启动容器并返回 warming page。如果 wake path 失败,检查控制平面日志。
4. 应用监听地址
检查容器内应用监听的地址和端口。
判定方法:进入容器查看应用配置。
docker exec sandbox123 netstat -tuln
应用应该监听 0.0.0.0:3000,而不是 127.0.0.1:3000。Docker 官方文档说明:绑定到 127.0.0.1 或 ::1 的端口只有 Docker host 能访问,外部请求无法到达。
如果应用监听 localhost,修改应用配置或用 --network host 模式运行容器。
5. 端口绑定检查
检查 Traefik 配置的端口和容器应用端口是否匹配。
判定方法:对比 Traefik labels 和容器内部端口。
Traefik labels 里:
- "traefik.http.services.sandbox123.loadbalancer.server.port=3000"
容器应用应该监听 3000 端口。如果 Traefik 配置了 3000,应用监听 8080,请求会失败。
端口不匹配时,修改 Traefik labels 或应用配置。
结论
单机 Docker + Go 控制平面能搭建一套自托管预览环境,每个 sandbox 有独立 preview URL,资源有限制,安全有边界。但这套方案有适用条件:内部团队、受信任用户、小规模并发。如果信任边界扩展到外部陌生人,或并发需求超过单机容量,需要升级到 microVM 或 K8s。
核心模块回顾:判断表帮你快速选方案;架构拆解展示了控制平面、Traefik、SQLite 和 reaper 如何协同;Preview URL 实现依赖 Traefik Host rule 和 Docker labels;安全边界强调 Docker socket = 主机 root 权限;资源限制是多租户环境的底线;镜像缓存应对 Docker Hub 限流;排障清单帮你快速定位 preview URL 不可达问题。
下一步:
- 内部团队小规模预览 → 尝试单机 Docker + Go 控制平面,验证需求和资源密度
- 外部用户或高风险场景 → 升级到 Docker 官方 Sandboxes 或 Firecracker microVM
- 自托管 CI Runner → 参考 GitHub Actions self-hosted runner 实战指南,构建完整的自托管基础设施
- 预览环境里的应用部署 → 参考 Next.js Docker 自托管实战,把应用跑在 sandbox 里
自托管 Dev Sandbox MVP 落地流程
从单机 Docker 验证到内测前安全检查的实践步骤。
⏱️ 预计耗时: 4 小时
- 1
步骤 1: 准备干净主机
选一台只运行 sandbox 相关服务的 Linux 主机,不混放生产数据库、CI runner 或其他高价值服务。 - 2
步骤 2: 配置预览域名
本地先用 `*.localhost` 验证,真实环境再把 `*.preview.example.com` 指向主机并配置 TLS。 - 3
步骤 3: 跑通控制平面 API
至少验证 create、exec、stop、destroy、healthz,确保上层应用可以通过 API 管理环境。 - 4
步骤 4: 准备 sandbox 基础镜像
在基础镜像中预装 Node.js、Python、Git、常用包管理器和需要支持的 Agent CLI,减少每次启动后的重复安装。 - 5
步骤 5: 加入资源限制和回收策略
为每个 sandbox 设置 CPU、内存和 PIDs 限制,配置 idle stop、wake-on-request 和工作区持久化。 - 6
步骤 6: 锁住控制面和预览入口
启用 API token、内网访问或防火墙规则;敏感预览链接接入 forward-auth 或业务登录态。 - 7
步骤 7: 补日志和监控
记录创建、停止、销毁、执行命令和 Agent task,监控主机内存、磁盘、容器数量、冷启动时间和 502 比例。 - 8
步骤 8: 演练备份恢复
备份 SQLite、工作区目录、`.env` 和基础镜像构建脚本,并确认能在新主机恢复。
常见问题
Dev Sandbox 和普通 Docker Compose 有什么区别?
为什么不用 Kubernetes?
Docker 容器隔离可以直接跑陌生用户代码吗?
Preview URL 一定要 HTTPS 吗?
空闲停止后文件会丢吗?
Docker Hub 限流会影响 Dev Sandbox 吗?
13 分钟阅读 · 发布于: 2026年6月5日 · 修改于: 2026年7月14日



评论
使用 GitHub 账号登录后即可评论