- Ship /opt/deepseek-harness/web.cordis.patch.yml and pass it via upstream's --patch launcher flag (must precede the web command's own flags) instead of seeding DSH_HOME; entrypoint accepts template Post Arguments verbatim. - Add /workspace (agent WORKDIR) and pin DSH_HOME=/home/node/.dsh; drop the implicit /home/node volume so templates mount .dsh and /workspace cleanly. - README: Unraid template (repo/support/icon/versions corrected for this image, lanmode plugin caution), first-run chown note, DeepSeek logo. Verified: no-args, flags-only, and full template invocations all serve the web UI; trust fence still 403s unknown Hosts; state persists to mounted .dsh. Co-Authored-By: Claude Fable 5 <[email protected]>
deepseek-harness — trusted mirror & container build
Mirror of the official deepseek-ai/deepseek-harness with a locally-built, auditable container image for homelab use (Unraid).
Branch layout
| Branch | Contents |
|---|---|
master |
Exact mirror of upstream master (plus all dsh-v* tags). Never edited locally. |
main |
This branch: Dockerfile, entrypoint, and the sync/build workflow. |
Image
gitea.milesward.dev/mward4/deepseek-harness:<version>
gitea.milesward.dev/mward4/deepseek-harness:latest
Built from node:22-bookworm-slim + the official @deepseek-ai/dsh npm
release, pinned to the exact version. Upstream publishes no container image;
this repo exists so the image provenance is:
upstream tag → upstream npm release → this Dockerfile → this registry.
No upstream code is modified. Upstream deliberately refuses --host 0.0.0.0
on the CLI (the web UI can execute code, so exposing it to a network is a
deliberate opt-in); the supported opt-in is a --patch launcher layer, which
this image ships at /opt/deepseek-harness/web.cordis.patch.yml
(bind 0.0.0.0:3080). The UI is reachable only through ports you explicitly
publish, and the /api trust fence still refuses any Host it doesn't know.
Paths inside the container:
| Path | Purpose |
|---|---|
/home/node/.dsh |
dsh state: settings, credentials, sessions, plugins (DSH_HOME) |
/workspace |
The agent's working directory (container WORKDIR) |
/opt/deepseek-harness/web.cordis.patch.yml |
Baked web-server bind patch |
Entrypoint behavior: no arguments → dsh web --patch <baked patch> --no-open (the --patch launcher flag must precede the web command’s own flags);
arguments starting with a flag (e.g. --trusted-host tower.local) are
appended to that default; a full command line (e.g.
web --patch /opt/deepseek-harness/web.cordis.patch.yml --no-open) runs as
given. Runs as user node (UID/GID 1000).
Unraid template
First run (required): the container runs as UID 1000. Before starting, from the Unraid terminal:
mkdir -p /mnt/user/appdata/deepseek-harness/workspace
chown -R 1000:1000 /mnt/user/appdata/deepseek-harness
Otherwise dsh fails with EACCES on /home/node/.dsh.
<?xml version="1.0"?>
<Container version="2">
<Name>DeepSeek-Harness</Name>
<Repository>gitea.milesward.dev/mward4/deepseek-harness:latest</Repository>
<Registry>https://gitea.milesward.dev/mward4/deepseek-harness</Registry>
<Network>bridge</Network>
<Shell>sh</Shell>
<Privileged>false</Privileged>
<Project>https://github.com/deepseek-ai/deepseek-harness</Project>
<Support>https://gitea.milesward.dev/mward4/deepseek-harness/issues</Support>
<Overview>
DeepSeek Harness (dsh) - browser-based agent runtime from DeepSeek AI, packaged from the official npm release (built and served from this Gitea; non-root).
FIRST RUN (required): the container runs as UID 1000. Before starting, from the Unraid terminal:
mkdir -p /mnt/user/appdata/deepseek-harness/workspace
chown -R 1000:1000 /mnt/user/appdata/deepseek-harness
Otherwise dsh fails with EACCES on /home/node/.dsh.
SECURITY - READ BEFORE EXPOSING: the dsh Web UI has no authentication or TLS, and its web API can execute code on this container. Anything that can reach the port can run commands. Keep it on a trusted LAN, or put an authenticated reverse proxy (e.g. Cloudflare Access, Authelia) in front. Never port-forward it directly to the internet.
REMOTE ACCESS: dsh rejects non-loopback requests (HTTP 403 on /api) unless the address you browse from is trusted. To use the Web UI from another machine, edit Post Arguments and append: --trusted-host YOUR-UNRAID-IP-OR-HOSTNAME
The Settings/credentials pages are additionally restricted to loopback pages by upstream design; edit them via SSH tunnel (ssh -L 3080:127.0.0.1:3080 root@unraid-ip, then browse http://127.0.0.1:3080). The @goodandready/dsh-lanmode community plugin can lift that restriction, but it is third-party code from an unaffiliated author that disables an upstream security fence - review it yourself before trusting it with your credentials.
VERSIONS: this registry publishes immutable version tags AND a :latest tag that CI moves to each new official npm release. dsh is a developer preview with breaking changes between releases - pin a version tag if you want to update deliberately.
Your DeepSeek API key can be set below (stored in the container env) or entered later in Settings once running.
</Overview>
<Category>AI: Tools:</Category>
<WebUI>http://[IP]:[PORT:3080]</WebUI>
<TemplateURL/>
<Icon>https://gitea.milesward.dev/mward4/deepseek-harness/raw/branch/main/assets/deepseek-logo.png</Icon>
<ExtraParams/>
<PostArgs>web --patch /opt/deepseek-harness/web.cordis.patch.yml --no-open</PostArgs>
<Config Name="WebUI Port" Target="3080" Default="3080" Mode="tcp" Description="dsh Web UI. Reachable by everything on your LAN - see the security note in the Overview." Type="Port" Display="always" Required="true" Mask="false">3080</Config>
<Config Name="DeepSeek API Key" Target="DEEPSEEK_API_KEY" Default="" Description="Optional. Official DeepSeek API key (sk-...). Can also be configured later in the dsh Settings UI." Type="Variable" Display="always" Required="false" Mask="true"></Config>
<Config Name="DSH Home" Target="/home/node/.dsh" Default="/mnt/user/appdata/deepseek-harness" Mode="rw" Description="Persistent dsh state: settings, credentials, sessions, plugins. Must be owned by UID/GID 1000 (see Overview)." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/deepseek-harness</Config>
<Config Name="Workspace" Target="/workspace" Default="/mnt/user/appdata/deepseek-harness/workspace" Mode="rw" Description="The agent's working directory. Only mount data you are willing to let the agent read and modify. Must be writable by UID 1000." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/deepseek-harness/workspace</Config>
</Container>
To reach the UI from your LAN, append your Unraid host to Post Arguments, e.g.:
web --patch /opt/deepseek-harness/web.cordis.patch.yml --no-open --trusted-host tower.local --trusted-host 192.168.1.50
(port-less entries match any port). Without it, anything but a loopback Host
header gets a 403 from /api — that's upstream's DNS-rebinding/CSRF fence,
working as intended.
Keeping current
.gitea/workflows/sync-upstream.yml
runs every 6 hours on the Gitea Actions runners:
- Force-syncs upstream
masteranddsh-v*tags into this repo. - Resolves the official npm
latestversion of@deepseek-ai/dsh. - If the registry lacks an image for that version, builds and pushes it,
moving
:latestalong with it.
Validating an image
.gitea/workflows/validate-suspect-image.yml
(manual dispatch) forensically compares any third-party deepseek-harness
image already present on the runner host's docker daemon against this
repository's trusted build — without ever executing the suspect image
(docker create + filesystem export only). It reports: image
config/entrypoint/labels, whether any containers use it, a byte-level diff of
dsh's own code against the official release, dependency-tree drift, and
tampering red flags (patched-out 0.0.0.0 refusal, startup hooks, unexpected
network callouts).
Ran 2026-09-02 against runzhliu/deepseek-harness:0.1.1-rc.2: dsh code
byte-identical to the official npm release; no tampering found.