把 dsh(DeepSeek Harness agent)打包成 Docker 镜像, 随官方 npm 版本自动更新。
⚠️ 本仓库不含任何内网 IP/密钥。部署时cp .env.example .env, 填你的实际局域网 IP。
平台要求:仅 Linux(
network_mode: host依赖 Linux 语义,macOS/Windows 会静默失效,见安全章)。
cp .env.example .env # 必填 DSH_LAN_IP (你的部署机局域网 IP)
docker compose up -d # 启动
DSH_LAN_IP为强制项: 不填则 compose 拒绝启动(fail-closed)。这是刻意为之——缺省 loopback 会让 dsh 特权方法暴露全网(见安全章)。
- 上游源 = 唯一 npm 包
@deepseek-ai/dsh(自包含, 无需 GitHub 源码/asset) - 版本经 build-arg 绑定 npm version, 由 CI 定时轮询
dist-tags.latest自动重建:docker build --build-arg DSH_VERSION=0.1.1-rc.2 . - 镜像 tag 直接用 npm 版本号 (如
0.1.1-rc.2,latest) - 发布到 repo-scoped
ghcr.io/snailium/dsh-container/dsh(跟随仓库: 公开仓库→镜像公开; 也可 Web UI 单独改成 private)
.github/workflows/update.yml 定时轮询 npm dist-tags.latest:
- 有新版 →
docker build --build-arg DSH_VERSION=<新版本>→ 推 ghcr - 部署机
docker compose pull && up -d即升级
| 环境变量 | 默认 | 说明 |
|---|---|---|
DSH_WEB_PORT |
3080 | dsh web HTTP 监听端口 |
DSH_HTTPS_PORT |
8443 | HTTPS(TLS relay)访问端口 |
DSH_LAN_IP |
必填 | 部署机局域网 IP (证书 SAN + 受信权威)。无默认, 缺失 compose 拒绝启动 |
DSH_TRUSTED_AUTHORITY |
{LAN_IP}:3080 | relay 把外部 Host 重写成的受信权威。独立可配 |
DSH_RELAY_BIND_LOOPBACK |
0 | 1 时 relay 只绑本机(配 loopback 权威用于本机专用) |
改 HTTPS 端口(避开冲突): 编辑 .env
echo "DSH_HTTPS_PORT=8444" >> .env
docker compose up -d访问地址相应变成 https://<你的DSH_LAN_IP>:8444。
证书放在数据卷 /dsh-home/tls/, 不属于镜像。 启动时 entrypoint 处理:
| 现状 | 行为 |
|---|---|
cert.pem+key.pem+server.pem 齐全 |
直接复用 |
只有 cert.pem+key.pem (导入的旧证书) |
自动合并生成 server.pem |
| 全无 | 自动生成新自签证书 (含 DSH_LAN_IP + localhost SAN) |
把已有 CA/自签证书拷进数据卷 tls 目录即可(重启容器生效):
cp my-cert.pem <数据卷>/tls/cert.pem
cp my-key.pem <数据卷>/tls/key.pem
rm -f <数据卷>/tls/server.pem # 让 entrypoint 重新合并
docker compose restart dsh自签证书需要浏览器「继续前往」例外一次(CN+SAN=IP, Chrome 会记住)。
dsh 有三个东西要挂,缺一不可(尤其工作区):
| 数据 | 宿主路径(.env 填) | 容器内 | 说明 |
|---|---|---|---|
| 设置/数据根 | $DSH_DATA_DIR |
/dsh-home |
$DSH_HOME: settings.yaml、profiles/cordis.patch.yml、.credentials.yaml、sessions、storages、integrations、tls |
| 工作区 |
$DSH_WORKSPACE_DIR |
/workspace |
宿主工作区目录挂到容器内固定 /workspace。dsh 核心的工作区由 UI 显式新建(create<path> 指定目录),容器内从 /workspace 建工作区即可。挂载解耦:换宿主路径不改容器 |
| TLS 证书 | <$DSH_DATA_DIR>/tls |
/dsh-home/tls |
见上节, entrypoint 自动管理 |
⚠️ 工作区挂载:本镜像固定把$DSH_WORKSPACE_DIR挂到容器内/workspace,WORKDIR=/workspace。要用工作区就在 UI 里新建工作区、目录选容器内的/workspace(或其子目录)。统一挂载的好处是宿主路径与容器内路径解耦——换宿主目录时只需改.env的DSH_WORKSPACE_DIR,容器和会话路径都不用动。关于会话与工作区:dsh 核心会按会话 header 固化的
cwd(+realpath) 派生工作区归属;换工作区路径会让旧会话落到 Ungrouped(数据不丢、UI 分组错位)。若需把旧工作区上的会话迁移到新路径,见仓库内 session-migration.md。
配置 = bind-mount 整块 harness-home → 升级只换镜像, 数据永不丢。
这个容器化 dsh 的主要用途是自动化测试(如 B70 每次更新后端后的 agent 测试)。生产使用建议裸机部署;容器作为隔离测试 harness最方便。
核心结论:headless CLI 完全不受 dsh 0.1.2 的 access-token 影响——headless 模式跑单任务走 CLI 独立通道,不经 web 访问层,零 token、零认证,天然适合自动化测试。
# 创建一个测试 DSH_HOME 卷(首次会自动 seed headless profile + 装 web 搜索插件)
mkdir -p /home/user/dsh-test-home
# settings.yaml: 声明被测 provider + 默认模型(按你的后端填)
# 例: llm-pi-ai.providers.<name> + agent-default-model 指到它(或用 --patch 覆盖)
# 跑一个 headless 单任务 (担默认 entrypoint, 非 docker run --entrypoint dsh)
docker run --rm --network host \
-v /home/user/dsh-test-home:/dsh-home \
-e DSH_MODE=headless -e DSH_TEST_PROFILE=headless_wsp \
-e DSH_HOME=/dsh-home -e <API_KEY_ENV>=<key> -e HOME=/root \
<image> --patch /dsh-home/patch.yml "你的测试任务"
# 首次启动: 自动 seed profile + pnpm 装插件 → 跑任务。之后复用卷每次跳过安装直接跑。
# headless 不需要 DSH_LAN_IP(不做网络访问), 也不起 relay/TLS。DSH_MODE=headless+DSH_TEST_PROFILE(默认模板建于headless_wsp) → entrypoint 自动确保 profile + 插件就绪后exec dsh --profile <p> <任务>- 首次启动自动安装自带插件; 数据卷复用后跳过
--patch覆盖默认模型/provider(或写 DSH_HOME/settings.yaml)
headless 测试 agent 有 web 搜索需求, 镜像自带两个 dsh 0.1.2 修复版插件 tgz(在 /plugs/, 首次启动自动安装):
| 插件 | 版本 | 作用 |
|---|---|---|
@anweat/dsh-browser |
0.1.10 | 浏览器服务(web-search-pro 的必需依赖, inject: ['browser']) |
dsh-web-search-pro |
0.1.11 | 多引擎 web 搜索工具(web_search_pro / web_fetch_pro 等) |
⚠️ 这两个插件官方暂无 0.1.2 release——上游使用的settingsNamespace/installSettingsSection在 dsh 0.1.2 被移除, 旧版在 0.1.2 上加载即崩。本镜像内置的是本地修复+构建版(src:~/dsh-build/{dsh-browser-src,web-search-pro-src})。修复已提 PR 上游:anweat/dsh-browser#12+anweat/dsh-web-search-pro#19。官方合并发布兼容版后可切换到官方 npm 包(届时更新 /plugs 及模板)。
cd <dsh-container 工程>
docker compose pull # 拉新版镜像(CI 已构建推送)
docker compose up -d # 重建容器, 数据卷原样复用dsh 的 Web UI 和 relay 都没有登录认证。 上游 fence 明确声明 "this fence is not an auth layer"。这意味着:
- 任何能连到 8443 的主机 都能读取全部会话/对话内容, 并通过非特权方法驱动 agent 执行(agent 能读写工作区!)
- 防火墙、WARP/Mesh 访问隔离、IP 白名单是唯一的真实防线——不是摆设, 是必需的
- 因此禁止把 8443 暴露到公网/不可信网络
- fail-closed 启动校验 (审查 S1):
DSH_LAN_IP必填(compose 校验 + entrypoint 双重保证), 缺省 loopback 时拒绝启动——绝不把特权面(bind-mount 的配置/凭据/TLS 私钥)暴露给全网 - 非 root 运行 (审查 H1): entrypoint(以 root 启动以管理证书/chown) 用
runuser -u node降权启动 dsh/relay 工作进程, 进程属主为 node(uid 1000), 无 root 权限 - 特权方法隔离: dsh 的
settings.*/credentials.*/host.pickDirectory/llm.discoverModels只认 loopback Host, 远程始终 403(改 Models 走配置驱动) - image 内不烘焙任何密钥/配置; 全运行时挂卷
# 只允许本机 web 端口 + 你实际需要访问的来源
sudo ufw default deny incoming
# 本机直接用
sudo ufw allow proto tcp from 127.0.0.1 to any port 3080,8443
# 局域网/WARP 来源(按需开启, 替换成你的网段/来源)
sudo ufw allow proto tcp from 192.168.1.0/24 to any port 8443更严格的做法: 完全不开放 8443 到 LAN, 仅通过 WARP/Mesh(Cloudflare) 访问; 或用
DSH_RELAY_BIND_LOOPBACK=1只在本机用。
- 仅 Linux (
network_mode: host+ relay 依赖 Linux 网络语义)。macOS/Windows Docker 的 host 网络语义不同, 会静默失效。
settings.*远程 403 是预期行为(设计如此), 不是 bug——远程改 provider 走配置文件DSH_TRUSTED_AUTHORITY若被你改成 entrypoint--trusted-host列表外的值, 所有 LAN 流量会 403(fail-closed); 排查时先确认两者一致
dsh 全局包绑定 node 版本, node20 会崩 createZstdDecompress。
本镜像锁定 node:22, 永不再踩——这是容器化的核心收益之一。