将 npm 上的 deepseek-ai/deepseek-harness(dsh)打包为 Docker 镜像并发布到 GitHub Container Registry,供服务器直接运行。
方案:npm install -g @deepseek-ai/dsh + 内置 Python loopback proxy。dsh 监听 127.0.0.1(天然通过 trusted-host 检查),proxy 监听 0.0.0.0 并改写 Host 头——无需 --network host、无需 TRUSTED_HOSTS、无需反代透传 Host,普通 -p 端口映射即可外部访问。
| 文件 | 作用 |
|---|---|
Dockerfile |
两阶段构建:builder 编译 node-pty 原生依赖 → runtime 仅拷贝结果 + python3 |
loopback-proxy.py |
TCP 转发代理:0.0.0.0:PORT → 127.0.0.1:DSH_PORT,改写 Host 头为 loopback |
entrypoint.sh |
后台启动 dsh → 等就绪 → 前台启动 proxy |
.github/workflows/docker.yml |
GitHub Action:自动构建并推送镜像到 ghcr.io |
docker-compose.yml |
服务器部署示例 |
- push 到 main/master(修改了构建相关文件时自动触发)
- 每天定时(UTC 19:00 / 北京时间凌晨 3:00):检查
@deepseek-ai/dsh@latest是否有新版本;该版本已构建过则自动跳过 - 手动触发(workflow_dispatch):可指定
dsh_version(默认latest)和platforms
镜像标签:latest / dsh-<npm版本号> / YYYYMMDD
推送本仓库到 GitHub 后 Action 自动构建。构建完成后在仓库 Packages 页面可见镜像(建议设为 Public)。
docker run -d \
--name dsh-web \
-p 8080:8080 \
-v /opt/dsh/data:/data \
-v /opt/dsh/workspace:/workspace \
--restart unless-stopped \
ghcr.io/<你的用户名>/dsh-dockeer:latest或使用 docker-compose(改好镜像地址后 docker compose up -d)。
浏览器打开 http://<服务器IP>:8080 即可使用——不会 403。
docker run -d -p 9000:8080 -e PORT=8080 ... # 宿主机 9000 → 容器 8080
# 或
docker run -d -p 8080:8080 -e PORT=8080 ... # 默认
PORT是容器内 proxy 的监听端口,需与-p右边一致。DSH_PORT(默认 18080)是 dsh 内部端口,一般不需要改。
容器附加参数会原样追加在 dsh web 命令末尾:
docker run ... ghcr.io/<你的用户名>/dsh-dockeer:latest --some-flagdsh 的 /api 层校验请求头里的 Host / Origin authority,非 loopback 返回 403。上游源码中 --trusted-host 被故意钉死不生效(discussion #128 确认:只有 Host 为 localhost 才能通过检查,127.0.0.1 都不行)。本镜像用 Python TCP 代理绕过:
浏览器 → 0.0.0.0:8080 (proxy) ──TCP──→ 127.0.0.1:18080 (dsh)
↓ 改写 HTTP 头
Host: localhost:18080
Origin: http://localhost:18080
Referer: http://localhost:18080/...
dsh 看到的永远是 localhost 回环 → 通过检查
- dsh 监听
127.0.0.1:18080,不暴露给外部 - Python proxy 监听
0.0.0.0:8080,TCP 级别转发,改写三个头:- Host →
localhost:18080(核心,通过 trusted-host 围栏) - Origin →
http://localhost:18080(浏览器 POST/PUT 请求会发,dsh 也校验) - Referer →
http://localhost:18080/...(保留 path,只替换 origin 部分)
- Host →
- 强制
Connection: close(避免 keep-alive 后续请求头不被改写) - 响应数据原样透传(包括 SSE/chunked 流式输出)
- 纯标准库,零依赖
⚠️ proxy 只是让 dsh 以为请求来自 localhost,不是访问控制。外部鉴权由调用方负责(防火墙规则、反代加认证等)。
dsh 的全部状态在 $DSH_HOME(默认 ~/.dsh)。本镜像固定 DSH_HOME=/data,务必挂载 /data 卷:
| 路径 | 内容 |
|---|---|
/data/settings.yaml |
模型路由设置(Settings → Models 页面写入) |
/data/.credentials.yaml |
API 密钥(Models 页面写入,UI 不可回读) |
/data/.env |
环境变量层 |
/data/profiles/ |
profile 目录(含 cordis.patch.yml 与插件) |
/data/storages/ |
会话与工作区状态 |
镜像在 /workspace 启动 dsh,因此默认 workspace root 就是 /workspace。首次使用 Web UI 时点击 Choose workspace 添加并选中 /workspace。
API 密钥两种方式:环境变量 DEEPSEEK_API_KEY(优先级最高)或 Web UI Settings → Models 填写。
docker build -t dsh:local . # 跟踪 npm latest
docker build --build-arg DSH_VERSION=0.1.0-rc.6 -t dsh:local # 锁定版本
docker run --rm -p 8080:8080 dsh:local- 镜像约 1.3~1.6 GB:node:24-slim + dsh 依赖树(~1 GB)+ python3(~15 MB)。
- 原生依赖:dsh 依赖
node-pty,npm 包未附带 linux-x64 预编译产物,builder 阶段用 python3/make/g++ 编译,工具链不进入最终镜像。 - proxy 限制:TCP 转发 +
Connection: close,不支持 WebSocket(dsh 生产环境不需要 HMR);每个 HTTP 请求新建 TCP 连接,对 dsh 使用场景无影响。改写LOOPBACK_HOST环境变量可切换回环主机名(默认localhost,如上游修复后可改为127.0.0.1)。 - 上游处于 developer preview,可能存在破坏性变更;重要环境用
dsh-<version>标签锁定。 - Action 使用内置
GITHUB_TOKEN推送 GHCR,无需配置 secret。