如何构建 dify-agent-runtime(shellctl)镜像并在容器中运行与验证路径隔离
【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify
dify-agent-runtime是 dify 仓库中用 Go 实现的 shellctl 服务端与运行时工具集:它在一个容器内以 tmux 托管每个 agent job,并通过 Landlock 沙箱限制每个 job 的文件系统访问。这篇文章对应的任务是:从仓库构建dify-agent-runtime镜像、以容器方式启动shellctl serve,然后通过实际的 job 请求验证路径隔离(Path Isolation)是否按预期生效,以及如何关闭隔离做对照。
前提条件(均来自 dify-agent-runtime/README.md):
- 宿主机为 Linux,且内核 ≥ 5.13——Landlock 依赖这一版本,在不支持的 kernel 上只会向 stderr 打印警告;
- 运行时依赖 tmux(Dockerfile 已在构建阶段安装);
- Go 1.26(仅在不用镜像、改用
make build本地编译时需要)。
构建镜像
仓库文档给出的构建命令,在仓库根目录执行(构建上下文是dify-agent-runtime/,Dockerfile 位于其docker/子目录):
docker build -f dify-agent-runtime/docker/Dockerfile \ -t dify-agent-runtime:latest \ dify-agent-runtime/注意:README 中该命令还带有一个--build-context agent=./dify-agent参数,用于提供 agent 侧构建上下文;如果你只构建 runtime 镜像、不依赖该上下文,可以直接去掉。镜像构建分两个阶段(见 Dockerfile):先用golang:1.26交叉编译出shellctl、shellctl-sanitize-pty、shellctl-runner-exit、shellctl-runner、dify-agent五个二进制,再基于python:3.12-slim-bookworm安装 bash、git、jq、tmux、ripgrep、tini 等工具,并内置 Node.js 24.20.0、pnpm 11.9.0、uv 0.8.9(构建时需能访问 nodejs.org 下载 Node 发行包并校验 SHASUMS256)。
镜像创建非 root 用户dify,工作目录为/home/dify,暴露 5004 端口,入口命令为:
shellctl serve --listen 0.0.0.0:5004启动容器并确认服务就绪
README 给出的运行方式:
docker run -d --name dify-agent-runtime \ -p 15004:5004 \ dify-agent-runtime:latest两个需要说明的点:
- 服务鉴权 token 通过环境变量
SHELLCTL_AUTH_TOKEN注入(见 config.go 中DefaultAuthTokenEnv = "SHELLCTL_AUTH_TOKEN")。仓库的集成测试(Makefile 的integration-up)均以-e SHELLCTL_AUTH_TOKEN=<token>方式启动容器;调用 API 时以Authorization: Bearer <token>携带。 - 就绪检查使用
GET /healthz。Makefile 中的等待逻辑是:每 2 秒curl -sf http://localhost:<host-port>/healthz,60 秒内返回成功即视为就绪;验收测试还确认/healthz返回 200 且响应体status字段为ok。对应的手动检查:
curl -s http://localhost:15004/healthz未就绪时先docker logs dify-agent-runtime查看容器日志。
用 job 请求验证路径隔离
服务就绪后,通过POST /v1/jobs/run提交脚本来验证隔离行为。以下命令中把15004替换成你实际映射的宿主端口,<token>替换为你注入的SHELLCTL_AUTH_TOKEN值。
隔离默认开启(SHELLCTL_ENABLE_PATH_ISOLATION未设置时即为启用)。用一条 job 同时验证「允许写工作区」与「拒绝写 /tmp」:
curl -s -X POST http://localhost:15004/v1/jobs/run \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ -d '{ "script": "touch \"$PWD/iso-test\" && echo workspace_ok; touch /tmp/iso-denied 2>&1; echo tmp_exit=$?", "cwd": "/workspace", "timeout": 10 }'预期在output中看到workspace_ok,以及touch写/tmp失败(非零退出,如Permission denied)。这套断言与 acceptance_test.go 中TestLandlockUsesWorkspaceAsTempSpace的判定方式一致。
再看隔离的默认权限边界(README 中的表格,$HOME与 job 的cwd可读写,cwd同时被用作TMPDIR/TMP/TEMP,其余默认拒绝):
| 访问级别 | 路径(默认值) |
|---|---|
| Read-Write | $HOME和 job 的cwd(同时用作TMPDIR、TMP、TEMP) |
| Read-Write (dev) | /dev/null、/dev/zero、/dev/urandom、/dev/random、/dev/tty |
| Read-Only + Exec | /usr、/bin、/sbin、/lib、/lib64、/etc、/proc、/opt/dify-agent-tools、/opt/homebrew、/snap |
| Denied | 其余一切(/tmp、其他 agent 的 home、/var、/srv等) |
一条 job 同时验证「允许写 HOME」和「拒绝写 /opt」:
curl -s -X POST http://localhost:15004/v1/jobs/run \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ -d '{ "script": "touch \"$HOME/landlock-test-file\" && echo home_ok; touch /opt/landlock-denied 2>&1; echo exit=$?", "env": {"HOME": "/home/dify"}, "timeout": 10 }'预期output含home_ok,且exit=0不出现(写/opt被拒绝)。
另外一点:隔离开关只在容器级生效。验收测试TestLandlockEnvBypassBlocked验证了把SHELLCTL_ENABLE_PATH_ISOLATION: "false"放进 job 的env字段不能绕过沙箱,job 内仍会被拒绝。
可选分支:关闭隔离做对照
若要验证「关闭隔离后 /tmp 可写」,需要单独再起一个带-e SHELLCTL_ENABLE_PATH_ISOLATION=false的容器(Makefile 的integration-up就是这样准备 no-isolation 容器的):
docker run -d --name dify-agent-runtime-noiso \ -p 15005:5004 \ -e SHELLCTL_AUTH_TOKEN=<token> \ -e SHELLCTL_ENABLE_PATH_ISOLATION=false \ dify-agent-runtime:latest就绪检查同样打http://localhost:15005/healthz,然后提交同样的touch /tmp/...job,这次应成功(测试断言输出含write_ok)。验证完成后清理容器会删除该容器及其数据:docker rm -f dify-agent-runtime-noiso。
可选分支:直接跑仓库集成测试
Makefile 提供了完整的自动化验证入口。make integration会构建镜像、随机端口启动隔离与 no-isolation 两个容器、等待/healthz就绪后执行:
go test -tags=integration -v -count=1 -timeout=300s ./tests/...退出时自动调用integration-down删除两个容器并移除状态文件。若需要分步操作(排查失败时保留容器),用make integration-up、make integration-logs、make integration-down三个目标分别对应启动、看日志、清理;integration-test要求先有integration-up生成的状态文件。
限制与相关配置
- Landlock 要求 Linux ≥ 5.13;不支持的 kernel 上服务端只会在 stderr 打印警告,路径隔离不会真正生效。
- 环境变量清单见 internal/envvar/envvar.go:
SHELLCTL_ENABLE_PATH_ISOLATION(默认启用,仅值true时视为开启)、SHELLCTL_LANDLOCK_RW_PATHS/SHELLCTL_LANDLOCK_RO_PATHS/SHELLCTL_LANDLOCK_RW_DEV_PATHS(逗号分隔,覆盖默认的额外读写/只读+执行/设备路径)。按 landlock/config.go 的说明,变量一旦设置(即使为空字符串)就会整体替换默认值,设为空即不再授予$HOME之外的额外路径。 - job 输出文件约定(README):
pty模式(默认)合并 stdout/stderr 写入output.log;stdio模式 stdout 进output.log、诊断信息进stderr.log,且不接受/input。
【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考