在飞牛 NAS 上用 Docker 运行 DeepSeek Harness
一、这是什么
DeepSeek Harness(下文简称 dsh)是一个跑在本地、基于网页的 AI 工作台。它本身不提供模型,而是把你已有的各种大模型 API(DeepSeek、OpenAI -compatible、Ollama 等)聚合到一起,统一管理 provider、预设(preset)、凭据和插件,还能跑 agent。
简单说,它是一个「自己托管的 AI 操作台」:
- 配置一次 API Key,之后在网页里切换不同模型、不同预设来对话;
- 支持插件和工作区,agent 能直接读写文件;
- 数据全在自己机器上,不依赖任何第三方 SaaS。
因为官方没有放出成品镜像,本文用的是社区维护的 runzhliu/deepseek-harness 镜像。
二、部署完能怎么用
部署好之后,dsh 会作为一个应用出现在飞牛的 watchcow 面板里。点一下图标就能打开,不用记 IP、不用记端口:
- 在外网,通过飞牛分配给你的 watchcow 域名访问,且必须先登录飞牛账号;
- 在局域网,直接走
https://<你的 NAS 局域网 IP>:<端口>打开。
打开后就是 dsh 的主界面,左边选 provider、配预设,右边直接对话,跟用本地软件一样。


三、搭建步骤
下面按步骤来。整体思路是:用 docker-compose 起两个容器,一个跑 dsh 本体(只在内部网络,不对外暴露),一个跑 nginx 反代(对外暴露端口、处理 HTTPS 和请求头改写),最后用 watchcow 把入口收进飞牛面板。
第 1 步:准备目录和证书

唉

在存储池里建好 dsh 的数据目录和工作区目录,例如:
<你的存储池路径>/docker/deepseek-harness/dsh:dsh 的数据目录,API Key、会话、插件配置都存在这;<你的存储池路径>/deepseek-harness:工作区,给 agent 读写文件用。
证书方面,飞牛系统里自带证书管理,去飞牛的「证书」功能里把对应域名的证书下载/导出出来,放进项目目录的 certs/ 文件夹,命名为 fullchain.crt 和 private.key。后面 nginx 会用这两个文件在 443 上终止 TLS。
注意:飞牛 watchcow 网关对 https 服务做的是 TLS 透传,网关自己不解密流量,所以证书必须挂在后面的 nginx 容器里,而不是指望网关帮你处理。
第 2 步:在飞牛上装 watchcow 应用并绑定域名
在飞牛应用中心装好 watchcow(就是那个能把 docker 服务收进统一面板的工具)。装好后,给 dsh 预留一个域名(在 watchcow 里添加应用时填写),这个域名之后就是对外访问地址。
这一步本质上是「先占好入口」,后面的 docker 服务只要带对的 label,就会被 watchcow 自动识别并挂上这个域名。
第 3 步:写 docker-compose
新建一个目录放项目文件,结构大致如下:
deepseek-harness/
├── docker-compose.yml
├── config/
│ └── nginx-config.conf
└── certs/
├── fullchain.crt
└── private.key

docker-compose.yml 里定义两个服务:
deepseek-harness:用runzhliu/deepseek-harness:0.1.0-rc.6镜像,挂上面建好的两个目录,不映射端口,只在内部网络里跑;nginx:用nginx:alpine,把宿主端口(这里用11013)映射到容器443,挂上 nginx 配置和证书目录,并带上 watchcow 的 label(下面第 5 步细说)。
两个服务连同一个自定义的内部网络,dsh 只在内部可达,nginx 通过服务名 deepseek-harness:3080 访问它。
完整 docker-compose.yml:
yaml
services:
deepseek-harness:
image: runzhliu/deepseek-harness:0.1.0-rc.6
container_name: deepseek-harness
restart: always
networks:
- dsh-internal
volumes:
# 数据目录:API Key、会话、插件配置都在这
- <你的存储池路径>/docker/deepseek-harness/dsh:/home/node/.dsh
# 工作区:给 agent 读写文件用
- <你的存储池路径>/deepseek-harness:/workspace
environment:
- DSH_TELEMETRY_DISABLED=1
# 不映射 ports,仅在内部网络可达
nginx:
image: nginx:alpine
container_name: dsh-nginx
restart: always
networks:
- dsh-internal
ports:
- "11013:443" # 飞牛网关 TLS 透传,nginx 在 443 终止 TLS
volumes:
- ./config/nginx-config.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- deepseek-harness
labels:
watchcow.enable: "true"
watchcow.display_name: "deepseek-harness"
watchcow.icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/deepseek-dark.png"
watchcow.main.service_port: "11013"
watchcow.main.protocol: "https"
watchcow.main.title: "dsh"
watchcow.main.all_users: "false"
watchcow.main.ui_type: "url"
networks:
dsh-internal:
driver: bridge
上面
<你的存储池路径>替换成你自己的实际路径即可。
第 4 步:配 nginx 反代
这是关键。dsh 有几道安全限制,从外部直接反代会被拦,所以 nginx 必须做两件事:
- 把请求头里的
Host改写成127.0.0.1:3080(骗过 dsh 的回环校验); - 把
Origin头剥掉(dsh 在 Origin 不存在时直接放行)。
同时 nginx 要 listen 443 ssl,挂上第 1 步准备的证书,并做好 WebSocket 升级头、长超时等配置(AI 会话可能很久不断开)。
只改 Host 不剥 Origin,或者只剥 Origin 不改 Host,都会得到 HTTP 403,两个动作缺一不可。
完整 config/nginx-config.conf:
nginx
upstream dsh_backend {
server deepseek-harness:3080;
keepalive 32;
}
server {
listen 443 ssl default_server;
ssl_certificate /etc/nginx/certs/fullchain.crt;
ssl_certificate_key /etc/nginx/certs/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 100m;
location / {
# 核心:改写 Host 为回环 + 剥掉 Origin / Sec-Fetch
proxy_set_header Host 127.0.0.1:3080;
proxy_set_header Origin "";
proxy_set_header Sec-Fetch-Site "";
proxy_set_header Sec-Fetch-Mode "";
# 标准反代头
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
# WebSocket 支持(dsh 事件流)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 长超时,适配长时间 AI 会话
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
proxy_pass http://dsh_backend;
}
}
证书路径
/etc/nginx/certs/...对应第 1 步挂进去的certs/目录。host头里的Sec-Fetch-*一并剥掉,是为了让局域网用 IP 访问时不被判成跨站而拦掉。
第 5 步:配 watchcow 的 label
在 nginx 服务上加 watchcow 相关的 label,让面板识别它:
watchcow.enable: "true"开启识别;watchcow.display_name面板里显示的名字;watchcow.main.service_port填对外端口(如11013);watchcow.main.protocol填https;watchcow.main.ui_type填url(用新页面打开,比 iframe 内嵌更稳,局域网下也不会被证书问题卡住);- 再配个
watchcow.icon当图标。
第 6 步:启动
在 compose 目录里启动:
bash
docker compose up -d
启动后,dsh 容器和 nginx 容器都会 restart: always 自动拉起。去 watchcow 面板看一眼,deepseek-harness 应该已经出现在列表里了。
第 7 步:验证
- 外网:打开watchcow生成的应用(dsh),会跳转域名;
- 局域网:打开
https://<你的 NAS 局域网 IP>:11013,浏览器可能会报证书不匹配(因为证书是给域名签的),点「继续访问」即可。

能正常加载 provider 列表、能对话,就说明整套通了。
四、常见问题
1. 页面能打开,但一加载提供方目录 / 读取设置就报 403?
这是 dsh 的安全栅栏在拦。确认 nginx 同时做了 Host → 127.0.0.1:3080 改写 和 剥掉 Origin 头,缺一不可。
2. 网关直接报 502,nginx 日志里一堆 \x16\x03\x01 这种乱码?
说明 nginx 在 listen 80 上收到了 HTTPS 握手字节。飞牛网关是 TLS 透传,不会帮你解密,必须让 nginx 在 443 ssl 上挂证书自己终止 TLS。
3. dsh 容器一直重启 / 起不来,nginx 那边 Host unreachable?
检查 dsh 数据目录里的 .credentials.yaml 文件权限,必须是 600。权限不对 dsh 启动即崩,改成 600 即可。
4. 局域网用 IP 访问时被拦(cross-site)?
在 nginx 里把 Sec-Fetch-Site 和 Sec-Fetch-Mode 头也一起剥掉,dsh 就无法判定跨站而放行。另外建议 ui_type 用 url 而不是 iframe,内嵌在局域网下容易被证书问题卡住。
5. 访问时浏览器说证书不安全?
证书是签给你域名的,用 IP 访问时 CN 对不上,属正常现象。点「继续访问 / 仍要发送」即可;追求干净体验就用域名访问。
部署完成之后,平时要切模型、试预设、跑 agent,点一下面板图标就进去了,不用记端口,也不用裸跑服务。dsh 这套安全限制在没搞明白之前确实容易卡很久,但本质就是改写 Host + 剥 Origin 两件事,配好就稳了。