Docker deployment for DeepSeek Harness (dsh) with access through the WebUI.
cp .env.example .env # set DEEPSEEK_API_KEY and, if needed, TRUSTED_HOSTS
docker compose up -d --buildWebUI: http://localhost:3080 or http://<lan-host>:3080.
dsh web intentionally binds only to 127.0.0.1 (the CLI rejects --host 0.0.0.0).
The container therefore runs dsh internally on 127.0.0.1:3081 and forwards
0.0.0.0:3080 -> 127.0.0.1:3081 via socat.
The /api endpoint also enforces a browser trust fence: requests via a LAN IP or
hostname are only accepted if the authority is allowed through --trusted-host.
Set TRUSTED_HOSTS in .env (comma-separated, with port):
TRUSTED_HOSTS=192.168.1.10:3080,myserver:3080
Then run docker compose up -d again.
The WebUI has no built-in authentication — anyone who can reach port 3080 gets a fully agent-capable session. Do not expose it directly to the internet or an untrusted network.
If you need access from outside your own network, put a reverse proxy with authentication in front of it (HTTPS-terminating, e.g. Traefik, Caddy, or nginx with forward auth / basic auth, or an SSO layer such as Authelia or oauth2-proxy), and:
- bind the port to localhost only (
"127.0.0.1:3080:3080"incompose.yaml) and proxy to it, or restrict the port to the proxy's docker network instead of publishing it at all - add the public hostname to
TRUSTED_HOSTS(e.g.dsh.example.com:443)
| Path | Contents |
|---|---|
Volume dsh-data -> /data |
$DSH_HOME: profiles, installed plugins, .credentials.yaml, .env |
Volume dsh-workspace -> /workspace |
Workspace root of the agent; create workspaces here (/workspace/<name>) |
Install plugins with docker compose exec dsh dsh plugin --profile web add <pkg>;
they live in /data/profiles/web/ and survive container restarts and image rebuilds.
Runtime package installation:
| Kind | Installable at runtime? | Survives restart? | Survives rebuild? |
|---|---|---|---|
| dsh plugins | Yes | Yes (volume) | Yes |
| npm/pnpm packages in workspace | Yes | Yes (volume) | Yes |
System packages (apt) |
No — container runs as unprivileged node user |
— | — |
System packages belong in the Dockerfile; that is the only durable way.
DEEPSEEK_API_KEY— required for model access and web search; read from.envor/data/.env//data/.credentials.yamlTRUSTED_HOSTS— see aboveDSH_PORT— external port inside the container (default 3080)DSH_VERSION— pin the npm version: setDSH_VERSION=0.1.0-rc.7in.env(passed through as a build arg), thendocker compose up -d --buildGH_VERSION— GitHub CLI version in the image (default inDockerfile)PLAYWRIGHT_VERSION— Playwright version for browser tooling (default inDockerfile)UV_VERSION— uv version in the image (default inDockerfile)DEEPSEEK_BASE_URL/DSH_MODEL/DSH_SYSTEM_PROMPT— optional dsh config overrides (commented out in.env.example; authoritative reference:docs/config-catalog.mdin the dsh repo)
The image includes git, curl, and the GitHub CLI (gh) — see the next
section for the full toolchain. The agent in the
container runs as the unprivileged node user and cannot install system packages
at runtime — required tools belong in the image.
Authenticating with GitHub:
docker compose exec dsh gh auth login # interactive (device flow)
# or a token via .env: GH_TOKEN=ghp_... or GITHUB_TOKEN=...For commits, set the identity via .env (git reads these variables automatically;
they survive container recreation):
GIT_AUTHOR_NAME=Your Name
GIT_AUTHOR_EMAIL=you@example.com
GIT_COMMITTER_NAME=Your Name
GIT_COMMITTER_EMAIL=you@example.com
Note: gh auth login writes to ~/.config/gh in the container layer and is lost
when the container is recreated. A token via .env (GH_TOKEN) is the durable option.
The base image is node:24-bookworm (full buildpack-deps toolchain: gcc/g++/make,
git, curl, and the common build libraries are already included).
Preinstalled CLIs: gh, jq, rg, ffmpeg, socat, tree, tmux, htop,
rsync, sqlite3, plus python3 (3.11), python3-venv, pip, and uv.
- Python: system Python is PEP 668 "externally managed" — always use
python3 -m venv .venvoruv venvfor package installs. Other Python versions are available at runtime without root, e.g.uv venv --python 3.13. - Playwright: global
playwrightCLI/library plus Chromium at/ms-playwright(PLAYWRIGHT_BROWSERS_PATHis set image-wide). Headless works out of the box;xvfb-runis available for headed runs. - Pinning caveat: the bundled browsers match the image's global Playwright
version. A workspace project using a different Playwright version must run its
own
npx playwright install chromium(installs into/ms-playwrightas thenodeuser). - The container runs with
init: trueandshm_size: 1gbfor Chromium stability (zombie reaping, shared-memory headroom). - Fonts: Playwright pulls Liberation, FreeFont, Unifont, Noto Color Emoji,
ipafont-gothic (JP), wqy-zenhei (CN) and tlwg-loma (TH). For Korean or full
CJK coverage add
fonts-noto-cjkto the apt line in theDockerfile.
Beyond DeepSeek, the Web UI (Settings -> Models) supports catalog providers
(Anthropic, OpenAI, Bedrock, Vertex, Azure, Codex — the latter need native
credentials) and custom OpenAI-compatible endpoints. Keys are stored in
$DSH_HOME/.credentials.yaml and therefore persist via the /data volume.
Env vars (e.g. ANTHROPIC_API_KEY) are only needed for provider entries that
reference them via apiKeyEnv (see .env.example).
dsh confines agent subprocesses with Landlock (landlock-run, prebuilt binaries
ship with dsh via npm — no image support needed). Enforcement is fail-closed and
depends on the host: kernel 5.13+ with Landlock enabled and a Docker version
whose default seccomp profile permits the landlock_* syscalls (current Docker
does; not pinned to a specific minimum version). On WSL2/Docker Desktop the WSL2
kernel determines availability — check the runtime status
(docker compose logs dsh | grep -i -E 'landlock|sandbox') rather than
assuming. If unsupported, dsh reports the sandbox as unusable instead of
silently running unconfined.
DeepSeek Harness is in developer preview; breaking changes between versions are
expected. If an update causes problems, pin the version (see DSH_VERSION).
The dsh repo docs tree (docs/tool-catalog.md, docs/config-catalog.md,
docs/user/) is the authoritative reference for dsh behavior.