# DeepSeek Harness (DSH) Docker 部署指南 ## 概述 本仓库是 DeepSeek Harness 源码 + Docker 构建配置。镜像**只内置工具链**,不把 dsh 钉死在构建层:容器每次启动由 `docker/entrypoint.sh` 从 npm 安装官方 `@deepseek-ai/dsh@latest`(可用环境变量覆盖),安装失败则非零退出,不回退到旧版本。因此**启动时必须能访问 npm registry**。 ### 端口拓扑(当前实现) 官方 `dsh web` **只监听 `127.0.0.1`**,并明确拒绝 `--host 0.0.0.0`(安全原因:Web 面可远程执行代码)。Docker 的 `-p` 打不到容器内 loopback,故 entrypoint 额外起一层 L4 转发: ``` 宿主机 :3080 → 容器 0.0.0.0:3080 → 127.0.0.1:3081 (dsh web --no-open --port 3081) ``` ``` ┌──────────┐ http://localhost:3080/?token=… ┌─────────────────────────────┐ │ 浏览器 │ ────────────────────────────────▶ │ 0.0.0.0:3080 (转发) │ └──────────┘ │ ↓ │ │ 127.0.0.1:3081 (dsh web) │ └──────────────┬──────────────┘ ┌──────────────────────────────┼──────────────┐ │ dsh-data 卷 │ ./workspace │ │ /home/dsh/.dsh │ /workspace │ │ (会话/设置/插件/配置) │ (工作目录) │ └──────────────────────────────┴──────────────┘ ``` ### 浏览器认证(官方包行为) 官方 `@latest` **强制浏览器认证**,没有免 token 的开关: 1. 从 `docker logs` 复制带 `?token=` 的 URL,用浏览器打开**一次** 2. 服务端 `303` 下发 **30 天签名 cookie** 3. 之后同一浏览器直接访问 `http://:3080/` 即可 **说明**:历史 fork 镜像里的 `DSH_TRUST_ALL=1`、`DSH_TRUSTED_HOSTS` 环境变量、默认绑定 `0.0.0.0` 均**不在官方 npm 包中**。官方侧 Host/Origin 围栏可用 **`--trusted-host`**(compose `command` 传入)扩展非 loopback 权威;cookie 认证仍必须完成。 ## 镜像信息 **当前 Dockerfile 为「运行时拉取」模型**:构建产出工具链底座(约 747MB,linux/arm64 本地实测),dsh 版本由每次容器启动时的 `@deepseek-ai/dsh@latest` 决定,不体现在镜像 tag 里。 | 项目 | 值 | |------|-----| | 基础镜像 | `registry.cn-hangzhou.aliyuncs.com/skymirror/node:24.19.0-bookworm-slim`(`ARG BASE_IMAGE` 可覆盖;内容同 `docker.io/library/node:24.19.0-bookworm-slim`) | | dsh 安装 | 启动时 `npm install -g @deepseek-ai/dsh@latest`(默认 `registry.npmjs.org`) | | 宿主机端口 | `3080`(compose `3080:3080`) | | 容器内 dsh 端口 | `3081`(仅 `127.0.0.1`) | | ENTRYPOINT | `/usr/local/bin/dsh-entrypoint` | ### 历史镜像存档(已过时,勿按其行为部署) 以下 digest 对应**旧 fork 构建时安装**镜像(`0.1.2-alpha.2-skymirror.1`,`dist/` pack + `file:` 闭包、`DSH_TRUST_ALL`、默认 `0.0.0.0`)。当前 Dockerfile 已取代该模型,哈希仅备查: | 项目 | 值 | |------|-----| | manifest list | `sha256:4c8636d1f20804a39757829de5adb27dccf6435b99610e2a70e74fe8cc95b5bd` | | 更早 monorepo 编译镜像 | `sha256:8a5d6406abd56f5cc3f175aa6b751e226bc91430dfb896add1fcf9a3f970f39e` | ## 快速开始 ```bash git clone <仓库地址> cd DSH docker compose build docker compose up -d docker logs -f dsh # 等待出现类似: # dsh-entry: installed 0.1.5-rc.1 # dsh-entry: forwarding 0.0.0.0:3080 -> 127.0.0.1:3081 # dsh web: http://127.0.0.1:3081/?token=<一次性令牌> # 把日志 URL 中的 127.0.0.1:3081 换成 127.0.0.1:3080(或服务器 IP:3080)后打开 ``` 首次使用时在 Web UI 中配置模型 API Key(DeepSeek 或任意 OpenAI 兼容端点)。 国内网络建议在 compose 的 `environment` 里加: ```yaml - DSH_NPM_REGISTRY=https://registry.npmmirror.com ``` ## 启动环境变量与参数 ### 环境变量(`docker/entrypoint.sh`) | 变量 | 默认 | 说明 | |------|------|------| | `DSH_PACKAGE` | `@deepseek-ai/dsh` | 要安装的包名 | | `DSH_DIST_TAG` | `latest` | npm dist-tag 或精确版本(如 `0.1.5-rc.1`) | | `DSH_NPM_REGISTRY` | `https://registry.npmjs.org` | 拉包 registry;国内可设 `https://registry.npmmirror.com` | | `DSH_NPM_CACHE` | `$DSH_HOME/npm-cache` | npm cache(`dsh-data` 卷内,重启可命中) | | `DSH_INTERNAL_PORT` | `3081` | dsh 监听端口(仅 `127.0.0.1`) | | `DSH_FORWARD_PORT` | `3080` | entrypoint 在 `0.0.0.0` 上的转发端口(Docker `-p` 应映射此端口) | ### 传给 `dsh web` 的参数 entrypoint 固定附加 `--no-open --port ${DSH_INTERNAL_PORT}`。compose 的 `command` 会**追加**在后面,例如: ```yaml command: ["--trusted-host", "203.0.113.10"] ``` | 参数 | 作用 | |------|------| | `--trusted-host ` | 向 Host/Origin 围栏追加非 loopback 权威(可重复);**不**替代 cookie 认证 | | `--port` / `--host` | 不要通过 command 覆盖:端口由 `DSH_INTERNAL_PORT` 管理;`--host 0.0.0.0` 会被官方拒绝 | ## 前置条件 - Docker Engine 20.10+(BuildKit) - Docker Compose v2 - 启动时能访问 npm registry(官方 npmjs 或 npmmirror) ## 一、不用 Docker 直接跑 ```bash # Node.js ^22.19 || >=24 npx --yes @deepseek-ai/dsh@latest web # 或 npm install -g @deepseek-ai/dsh@latest dsh web --no-open --port 3080 ``` 本机默认只监听 `127.0.0.1`,浏览器打开日志里的 `?token=` URL。管理插件需要 `corepack enable pnpm`。 查看可用版本: ```bash npm view @deepseek-ai/dsh dist-tags ``` ### npm 路径 vs Docker | | `npx @latest` | Docker(本仓库) | |---|---|---| | 启动耗时 | 首次拉包约 15s–数分钟(视网络/cache) | 镜像已有工具链;每次启动装 dsh(有 cache 更快) | | 构建 | 无 | 只装 apt/工具链,不编译 monorepo | | 工具链 | 仅本机 Node | Python / gcc·g++ / pnpm / Rust 按需 | | 升级 | 每次 `npx@latest` | 每次容器启动装 `@latest`(失败退出) | | 数据 | `$DSH_HOME`(默认 `~/.dsh`) | `dsh-data` → `/home/dsh/.dsh` | | 远程端口 | 只听 loopback | entrypoint `3080` → dsh `3081` | ## 二、本地构建并运行 ```bash docker compose build docker compose up -d ``` 构建不需要 `pnpm build`、`release:pack` 或 `dist/`。 ### 基础镜像拉不动时 Docker Hub 在部分网络不可达。可先把官方 node 镜像同步到自己 registry,再: ```bash docker buildx build \ --build-arg BASE_IMAGE=registry.cn-hangzhou.aliyuncs.com/skymirror/node:24.19.0-bookworm-slim \ --platform linux/arm64 \ -t dsh:local-arm64 --load . ``` ### 清理 buildx 缓存 ```bash docker buildx du docker buildx prune --filter until=24h # 或 docker buildx prune --all ``` ## 三、云效构建并推送 流水线「镜像构建」使用仓库根目录 `Dockerfile`,推送到 ACR。目标机: ```bash docker pull registry.cn-hangzhou.aliyuncs.com/skymirror/dsh:latest docker run -d --name dsh \ -p 3080:3080 \ -v dsh-data:/home/dsh/.dsh \ -v /path/to/workspace:/workspace \ -e DSH_NPM_REGISTRY=https://registry.npmmirror.com \ registry.cn-hangzhou.aliyuncs.com/skymirror/dsh:latest ``` 注意:推上去的是**工具链底座**;运行时仍会拉 npm 上的 dsh。请保证部署机可访问所配置的 registry。 ## 四、目录 / 端口映射 | 项目 | 说明 | |------|------| | 宿主机 `3080` → 容器 `3080` | entrypoint 的 `0.0.0.0` 转发口 | | 容器内 dsh `3081` | 仅 `127.0.0.1`,不要单独 `-p` 映射 | | `dsh-data` → `/home/dsh/.dsh` | 会话、settings、storages、profiles(插件)、npm-cache | | `./workspace` → `/workspace` | agent 工作目录;目录选择器默认起点(`HOME`) | | `$DSH_HOME/profiles/` | 插件与 profile,卷内**持久化** | | `dsh` 命令 | 启动时 `npm install -g` 装入 PATH,供 `docker exec` 管理插件 | 插件示例: ```bash docker exec -it dsh dsh plugin --profile web add # 改完插件需重启容器 docker restart dsh ``` ## 五、镜像内置语言环境 | 语言/工具 | 版本 | 说明 | |-----------|------|------| | Node.js | 24.19.0 | 基础镜像 | | npm | 11.17.0 | 全局升级 | | pnpm | 11.7.0 | corepack;`dsh plugin add` 使用 | | Python | 3.11 | apt,含 pip/venv/Python.h | | Rust | stable 按需 | 首次调用 `cargo`/`rustc`/`rustup` 时装进 `dsh-data` 卷 | | gcc / g++ / make | bookworm | 原生模块编译 | | git / curl | apt | | 不内置 JDK、Go(体积取舍)。需要时进容器自行安装。 > 构建默认走国内源:npm `npmmirror`、Debian 清华、rustup USTC、pip 清华。云效等可出网环境可用 `--build-arg NPM_REGISTRY=...` 等覆盖。 ### Rust 按需安装 `/usr/local/bin/ensure-rust` 包装器在首次调用时把 toolchain 装进 `$RUSTUP_HOME` / `$CARGO_HOME`(均在 `dsh-data` 卷)。可提前 `docker exec dsh cargo --version` 触发。 ## 六、自定义配置 ### 修改端口 ```yaml environment: - DSH_FORWARD_PORT=8080 # 容器 0.0.0.0 转发口 - DSH_INTERNAL_PORT=3081 # dsh 本机端口(一般不用改) ports: - "8080:8080" ``` ### 从其它机器访问 1. **Host/Origin 围栏**:浏览器地址为 `http://<服务器IP>:3080` 时,Host 不是 loopback,需在 compose 中追加: ```yaml command: ["--trusted-host", "<服务器IP或域名>"] ``` 2. **浏览器认证**:仍必须用日志里的 `?token=` URL 打开一次,换 30 天 cookie。 3. **传输与入口**:DSH 可执行代码。公网暴露请在前面加带认证/TLS 的反向代理,不要裸奔。 ### 挂载自己的数据目录 ```yaml volumes: - /data/dsh:/home/dsh/.dsh - /data/work:/workspace ``` ## 七、常见问题 | 现象 | 处理 | |------|------| | 启动很慢或退出 | 看 `docker logs`:多半是装 `@latest` 网络失败;设 `DSH_NPM_REGISTRY=https://registry.npmmirror.com` | | `authentication required; reopen the URL printed by dsh web` | 正常:用带 `?token=` 的 URL 打开一次;官方包**不能**免 token | | `GET /` 401 | 同上 | | `/api` 403 | Host 不在围栏内:给 `dsh web` 加 `--trusted-host <你的Host>` | | 宿主机 3080 连不上 / connection reset | 确认映射的是转发口 `3080:3080`,且 logs 有 `forwarding 0.0.0.0:3080 -> 127.0.0.1:3081` | | `--host 0.0.0.0 is intentionally not supported` | 官方限制;不要传 `--host 0.0.0.0`,依赖 entrypoint 转发 | | 端口被占用 | 改 compose 的 `ports` 与 `DSH_FORWARD_PORT` | | `Cannot find module .../dsh/lib/bin.js` | 自定义了 `entrypoint:`/`command:` 绕过安装入口;去掉覆盖后重建 | | 插件重启后消失 | 插件应装在 `$DSH_HOME/profiles`(卷内);确认未挂错卷 | | 设置了 `DSH_TRUST_ALL=1` 仍要 token | **预期行为**:官方包无此变量;历史 fork 镜像才有 | ## 附录:fork 私有 npm 发布(可选,当前 Docker 不用) 当前 Docker 路径**只消费官方 npm `@latest`**。若仍需维护 skymirror fork 并发到私有 registry,可继续用仓库内 release 脚本(与镜像构建解耦): ```bash pnpm run release:dsh pnpm run build:official pnpm run release:pack --family dsh --out dist/npm # … vendor / landlock / verify / publish,见 scripts/release/ ``` fork 的 `DSH_TRUST_ALL`、`DSH_TRUSTED_HOSTS` 环境变量、默认 `0.0.0.0` 绑定等部署补丁只存在于 fork 编译产物中;要在容器里用它们,需把 `DSH_PACKAGE` / `DSH_DIST_TAG` / `DSH_NPM_REGISTRY` 指到私有 registry 的 fork 包,并自行承担与官方 `@latest` 的行为差异。