群晖相关
群晖 Docker 部署 DeepSeek Harness(dsh)完整教程
群晖 Docker 部署 DeepSeek Harness(dsh)完整教程
写在前面
DeepSeek Harness(
@deepseek-ai/dsh)是 DeepSeek 官方的 Agent 框架,自带 Web UI,可以让 AI Agent 读写文件、执行命令、委派子任务。本教程基于 群晖 DS920+(J4125 / DSM 7.4 / Container Manager) 完整部署一套可用实例:- 公网入口:
http://你的域名.com:3080(nps 内网穿透,已开启账号密码登录) - 局域网入口:
http://你的群晖IP:3080(HTTP,已内置兼容补丁)或https://你的群晖IP:3443(自签证书 HTTPS) - 工作目录:
/volume1/deepseek(放在群晖 RAID 盘上,稳定持久) - 默认模型:
deepseek-v4-flash,API Key 通过DSH_HOME下的凭据文件配置 - 登录:nginx basic auth,用户名
dsh(密码部署时设定,见下文) - PVE LXC:部署简单、管理方便,但存储通常是单盘,没有 RAID;
- 群晖 Docker:数据在 RAID 阵列上,工作目录天然稳定,适合存放 Agent 长期产生的项目文件。
> 本文所有命令默认你已经通过 SSH 登录群晖(把
你的群晖用户名 换成实际账号),并拥有 sudo 权限。一、为什么用群晖而不是 PVE
两个方案都能跑,区别在数据安全性:
最终方案是 服务跑在群晖 Docker 容器里,工作目录直接落在群晖存储卷上,兼顾稳定和易用。
二、准备目录
# 工作区(Agent 读写项目文件的目录,放在 RAID 盘上)
sudo mkdir -p /volume1/deepseek
# dsh 构建目录(Dockerfile、nginx 配置、DSH_HOME 数据)
sudo mkdir -p /volume1/docker/dsh/home
/volume1/deepseek 就是之后 Web UI 里选择的工作区。三、国内网络加速(关键)
群晖直连
deb.debian.org 和 registry.npmjs.org 通常很慢甚至卡死,必须在 Dockerfile 里换源:mirrors.tuna.tsinghua.edu.cn(实测 45MB/s)https://registry.npmmirror.com四、Dockerfile
写入
/volume1/docker/dsh/Dockerfile:FROM node:22-bookworm-slim
ENV DSH_HOME=/opt/dsh
RUN sed -i 's|http://deb.debian.org/debian|http://mirrors.tuna.tsinghua.edu.cn/debian|g; s|http://security.debian.org/debian-security|http://mirrors.tuna.tsinghua.edu.cn/debian-security|g' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends build-essential python3 nginx openssl ca-certificates \
&& npm config set registry https://registry.npmmirror.com \
&& npm install -g @deepseek-ai/dsh \
&& rm -f /etc/nginx/sites-enabled/default \
&& rm -rf /var/lib/apt/lists/*
RUN openssl req -x509 -newkey rsa:2048 -nodes -keyout /etc/nginx/dsh.key \
-out /etc/nginx/dsh.crt -days 3650 -subj '/CN=你的群晖IP' \
-addext 'subjectAltName=IP:你的群晖IP,DNS:localhost' 2>/dev/null
COPY nginx.conf /etc/nginx/conf.d/dsh.conf
COPY .htpasswd /etc/nginx/.htpasswd
CMD ["sh", "-c", "nginx && exec dsh web --host 127.0.0.1 --port 3080 --trusted-host 你的群晖IP:3080 --trusted-host 你的群晖IP:3443 --trusted-host 你的域名.com --trusted-host 你的公网IP:3080"]
几个要点:
build-essential 必须有,node-pty 需要本地编译(没有会报 make: command not found);0.0.0.0,所以让 dsh 只监听 127.0.0.1,由 nginx 反向代理对外提供服务;--trusted-host 是给 dsh 的 /api 跨域防护墙声明合法访问来源;五、nginx.conf
写入
/volume1/docker/dsh/nginx.conf:server {
listen 80;
server_name _;
auth_basic "dsh";
auth_basic_user_file /etc/nginx/.htpasswd;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:3080;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
sub_filter_once off;
sub_filter_types text/html;
sub_filter '<head>' '<head><script>if(window.crypto&&!crypto.randomUUID){crypto.randomUUID=function(){var d=new Uint8Array(16);crypto.getRandomValues(d);d[6]=d[6]&15|64;d[8]=d[8]&63|128;var h=Array.from(d,function(b){return b.toString(16).padStart(2,"0")});return h[0]+h[1]+h[2]+h[3]+"-"+h[4]+h[5]+"-"+h[6]+h[7]+"-"+h[8]+h[9]+"-"+h[10]+h[11]+h[12]+h[13]+h[14]+h[15]}};</script>';
}
}
server {
listen 443 ssl;
server_name _;
auth_basic "dsh";
auth_basic_user_file /etc/nginx/.htpasswd;
ssl_certificate /etc/nginx/dsh.crt;
ssl_certificate_key /etc/nginx/dsh.key;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:3080;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
登录账号与密码(basic auth)
访问 dsh 需要账号密码(basic auth),nginx 层拦截,页面无法自助修改。生成密码文件写入构建目录(
dsh 是用户名,你的密码 换成你想设的密码):printf 'dsh:%s\n' "$(openssl passwd -apr1 '你的密码')" | sudo tee /volume1/docker/dsh/.htpasswd
之后每次访问页面、调 API 都要带这个账号密码。
> 提醒:本教程部署的实例用户名固定为
dsh;密码由部署者私下设定,避免公网入口被他人直接登录。需要更换时按下面步骤操作。修改用户名或密码:重新生成
.htpasswd 文件(把 新用户名 和 新密码 换成你要的值),然后重建镜像并重建容器即可生效:# 1. 重新生成密码文件(改用户名/密码都走这一步)
printf '新用户名:%s\n' "$(openssl passwd -apr1 '新密码')" | sudo tee /volume1/docker/dsh/.htpasswd
# 2. 重建镜像(基础层已缓存,很快)
cd /volume1/docker/dsh
sudo /var/packages/ContainerManager/target/usr/bin/docker build -t dsh:latest .
# 3. 重建容器(删除旧容器并重新运行)
sudo /var/packages/ContainerManager/target/usr/bin/docker rm -f dsh
sudo /var/packages/ContainerManager/target/usr/bin/docker run -d \
--name dsh \
--restart unless-stopped \
-e DSH_HOME=/opt/dsh \
-v /volume1/docker/dsh/home:/opt/dsh \
-v /volume1/deepseek:/workspace \
-p 3080:80 \
-p 3443:443 \
dsh:latest
> 注意:
.htpasswd 是打包进镜像的,所以改完必须重建镜像,不能只改群晖上的文件。为什么要在页面里注入 randomUUID 补丁
dsh 的 Web UI 在选择工作区时会调用浏览器 API
crypto.randomUUID(),而这个函数只在 HTTPS 或 localhost 的安全上下文中存在。局域网 IP 用明文 HTTP 访问时,浏览器没有这个函数,会报:crypto.randomUUID is not a function
两个解决办法:
https://你的群晖IP:3443,自签证书首次访问需点"继续前往");sub_filter 在页面里注入一个 UUID v4 补丁(上面的配置),这样普通 HTTP 也能正常使用。六、构建镜像
cd /volume1/docker/dsh
sudo /var/packages/ContainerManager/target/usr/bin/docker build -t dsh:latest .
实测:apt 走清华源 87MB 两秒拉完,npm 532 个包 59 秒装完,整体约 5 分钟。
七、配置 API Key
sudo mkdir -p /volume1/docker/dsh/home
echo 'DEEPSEEK_API_KEY: sk-你的密钥' | sudo tee /volume1/docker/dsh/home/.credentials.yaml
sudo chmod 600 /volume1/docker/dsh/home/.credentials.yaml
该目录会挂载成容器里的
/opt/dsh(即 DSH_HOME)。八、运行容器
sudo /var/packages/ContainerManager/target/usr/bin/docker run -d \
--name dsh \
--restart unless-stopped \
-e DSH_HOME=/opt/dsh \
-v /volume1/docker/dsh/home:/opt/dsh \
-v /volume1/deepseek:/workspace \
-p 3080:80 \
-p 3443:443 \
dsh:latest
注意端口映射:容器内 nginx 监听 80/443,所以宿主机映射为
3080:80、3443:443,别映射反了(映射到容器内不监听的端口会导致连接被重置)。九、验证
# 在群晖上(basic auth 已开启:不带账号返回 401,带上账号返回 200)
curl -sS -o /dev/null -w '%{http_code}\n' -u '用户名:密码' http://127.0.0.1:3080 # 200
curl -skS -o /dev/null -w '%{http_code}\n' -u '用户名:密码' https://127.0.0.1:3443 # 200
# 查看日志
sudo /var/packages/ContainerManager/target/usr/bin/docker logs dsh
浏览器访问(首次会弹出 basic auth 登录框):
http://你的域名.com:3080http://你的群晖IP:3080(推荐,无需证书)https://你的群晖IP:3443(首次有自签证书提示)十、开始使用
.credentials.yaml 提供,无需在页面再填);/workspace(对应群晖的 /volume1/deepseek)或其子目录;Summarize this repository and identify its main packages.
十一、常见问题
1. 构建时卡在 apt-get install
群晖访问
deb.debian.org 不通,换成清华源即可(本文 Dockerfile 已内置)。2. 提示 make: command not found
缺编译工具,
node-pty 需要 build-essential(本文已包含)。3. 端口映射后连不上(连接被重置)
检查映射关系:容器内 nginx 是 80/443,映射应为
3080:80、3443:443。4. 选工作区报 crypto.randomUUID is not a function
用 HTTPS 访问,或使用本文 nginx 注入的 randomUUID 补丁(HTTP 也可以)。
5. 公网访问
已完成:在 nps 控制台为 dsh 建立隧道,公网
http://你的域名.com:3080 指向本机 127.0.0.1:3080,同时 nginx 开启了 basic auth 登录。dsh 的 Agent 具备文件与命令执行能力,公网入口务必保持账号密码保护;如要 HTTPS 公网访问,可后续接 Cloudflare 隧道或 nps HTTPS 代理。附录:完整文件清单
/volume1/docker/dsh/
├── Dockerfile
├── nginx.conf
└── home/ # DSH_HOME,含 .credentials.yaml
/volume1/deepseek/ # 工作区(RAID 盘)
评论(0)