UI Enhancements

deepseek-harness-docker

alliottech/deepseek-harness-docker · ★ 22

alliottech

Unofficial Docker images for DeepSeek Harness with publishing pipelines for GHCR and Docker Hub.

License
MIT
Added
2026-08-23
Maintenance
active
Install kind
github

No manifest check is available; listing does not imply compatibility or security review.

安装

dsh plugin --profile web add github:alliottech/deepseek-harness-docker

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: en · Read Chinese README

Table of contents
  1. Image Registries
  2. Deployability Assessment
  3. Quick Start
  4. Secret Files
  5. Other Run Modes
  6. Environment Variables
  7. Remote Access
  8. Sandbox and Workspace Write (bwrap error)
  9. Publishing to GHCR and Docker Hub
  10. Customizing the Toolchain
  11. License and Attribution

中文文档

Build and publish container image Docker Pulls

This repository provides an unofficial Docker image for DeepSeek Harness, along with a publishing pipeline for both GHCR and Docker Hub. The source code lives in AlliotTech/deepseek-harness-docker.

Image Registries

Registry Image Page
GitHub Container Registry ghcr.io/alliottech/deepseek-harness GitHub Packages
Docker Hub alliot/deepseek-harness Docker Hub

Both registries publish identical linux/amd64 and linux/arm64 OCI manifests. We recommend pinning a container release tag for deployment:

docker pull ghcr.io/alliottech/deepseek-harness:0.2.0-rc.2
# or
docker pull alliot/deepseek-harness:0.2.0-rc.2

Available tags:

  • 0.2.0-rc.2: Container release; uses upstream DeepSeek Harness 0.2.0-rc.2 with the web startup fix and opt-in remote provider configuration support. Recommended for deployment.
  • dsh-0.2.0-rc.2: Latest container build for that upstream version; updated when wrapping-layer fixes are released. Mutable tag.
  • master: Latest build of this repository's default branch. Mutable tag.
  • sha-<commit>: A specific Git commit of this repository, e.g. sha-6ed1d92 (contains the web startup fix).
  • latest: Latest v* container release; convenient for trying things out, but use a full release tag or digest for strict pinning.

[!IMPORTANT] The early 0.1.0-rc.6 container releases had a web-startup defect (the web subprocess was missing --expose-internals) and a reverse-proxy configuration-plane HTTP 403, both fixed by 0.1.0-rc.6.2. The current recommended release is 0.2.0-rc.2 (upstream 0.2.0-rc.2). If you previously pulled the dsh-0.1.0-rc.6 or master mutable tags, re-run docker pull and recreate the container — Docker will not automatically replace old local images.

Deployability Assessment

Containerized deployment is feasible. As of 2026-08-14, upstream default branch commit 47f943859bef60e4160492346772ded9b24f765a provides npm package and source-based run modes, but the repository has no Dockerfile or Compose file, and no official GHCR/Docker Hub image was found. Upstream requires Node.js ^22.19.0 || >=24.0.0; the web UI, configuration, and session data all fit in a standard Linux container, and its Linux native components support amd64 and arm64.

This image installs the published @deepseek-ai/dsh npm package directly rather than replicating the upstream build chain. Dependencies are pinned via package-lock.json, the base image is pinned by digest, and the runtime uses Node.js 24, a non-root user, Tini, and a health check. Multi-arch images, SBOMs, and provenance are generated at release time.

[!WARNING] DeepSeek Harness can execute commands in the workspace. Since upstream 0.2.0 the web service requires browser authentication (the ?token= link printed at startup is exchanged for a session cookie), and upstream explicitly refuses to bind 0.0.0.0. This image keeps dsh listening on 127.0.0.1 inside the container and exposes the container port through a transparent TCP bridge. Always bind the Docker host port to 127.0.0.1; do not expose it to the public internet or an untrusted LAN.

Quick Start

Using the Docker Hub image and Compose:

git clone https://github.com/AlliotTech/deepseek-harness-docker.git
cd deepseek-harness-docker
cp .env.example .env
# Edit .env and fill in DEEPSEEK_API_KEY; you can also configure it in the local web UI after startup.
docker compose pull
docker compose up -d
docker compose logs -f

Open http://127.0.0.1:3080 in your browser.

Running the Docker Hub image directly:

docker run --rm -it \
  --name deepseek-harness \
  --security-opt no-new-privileges=true \
  --cap-drop ALL \
  -p 127.0.0.1:3080:3080 \
  -e DEEPSEEK_API_KEY \
  -v deepseek-harness-home:/home/node/.dsh \
  -v "$PWD:/workspace" \
  alliot/deepseek-harness:0.2.0-rc.2

/home/node/.dsh stores configuration, credentials, plugins, and sessions; /workspace is the project directory the Agent operates on by default. Only mount directories you are willing to let the Agent read and modify.

Upgrading an existing Compose deployment:

docker compose pull
docker compose up -d --force-recreate
docker compose ps

If you use docker run, re-pull the tag you use, then remove and recreate the container with the same arguments. You can confirm the old startup defect in the logs:

--expose-internals is required for HMR service

To build the current repository code locally:

docker build -t deepseek-harness:local .
DEEPSEEK_HARNESS_IMAGE=deepseek-harness:local docker compose up --build -d

Secret Files

In addition to DEEPSEEK_API_KEY, the entrypoint supports DEEPSEEK_API_KEY_FILE for mounting Docker/Kubernetes secrets as files:

docker run --rm -it \
  -p 127.0.0.1:3080:3080 \
  -e DEEPSEEK_API_KEY_FILE=/run/secrets/deepseek_api_key \
  --mount type=bind,src="$PWD/deepseek_api_key",dst=/run/secrets/deepseek_api_key,readonly \
  -v deepseek-harness-home:/home/node/.dsh \
  -v "$PWD:/workspace" \
  alliot/deepseek-harness:0.2.0-rc.2

Other Run Modes

Show the version or help:

docker run --rm alliot/deepseek-harness:0.2.0-rc.2 --version
docker run --rm alliot/deepseek-harness:0.2.0-rc.2 --help
docker run --rm alliot/deepseek-harness:0.2.0-rc.2 web --help

Run a one-off headless task:

docker run --rm -it \
  -e DEEPSEEK_API_KEY \
  -v deepseek-harness-home:/home/node/.dsh \
  -v "$PWD:/workspace" \
  alliot/deepseek-harness:0.2.0-rc.2 \
  --profile headless "Analyze the current project and run the tests"

Entrypoint rules: web or dsh web uses the container web bridge; arguments starting with - are passed to dsh; any other command is executed as-is, so bash also works.

Environment Variables

Variable Default Description
DEEPSEEK_API_KEY empty DeepSeek API key.
DEEPSEEK_API_KEY_FILE empty Secret file containing the API key; only read when DEEPSEEK_API_KEY is not set.
DEEPSEEK_BASE_URL upstream default Optional API-compatible base URL.
DSH_PORT 3080 Listen port of the container TCP bridge.
DSH_INTERNAL_PORT 3081 Internal port of dsh on the container loopback address; must differ from DSH_PORT.
DSH_TRUSTED_HOSTS empty Comma-separated additional host[:port] values; only for advanced deployments such as authenticated reverse proxies. It is not an authentication mechanism.
DSH_ALLOW_REMOTE_CONFIGURATION 0 Whether to let DSH_TRUSTED_HOSTS access the provider configuration API; only set to 1 behind an authenticated, HTTPS reverse proxy.
DSH_TELEMETRY_DISABLED 1 Telemetry is disabled by default in this image; set to an empty value to allow the upstream telemetry configuration.
DSH_TOOLS_MODE native Upstream supports native, code, or both.

The --host and --port of web mode are managed by the container entrypoint and cannot be passed directly. Adjust the host port via docker run -p or the DSH_HOST_PORT Compose variable.

Remote Access

Prefer SSH local forwarding: keep the container published only on the server's loopback address, then run from the client:

ssh -L 3080:127.0.0.1:3080 deploy@dsh.example.com

deploy@dsh.example.com above just represents the actual SSH user and address of your server.

If you must use a reverse proxy, it must provide reliable authentication, HTTPS, and proper WebSocket support. You also need to explicitly add the authority your browser uses to the trust list; otherwise the home page may open fine but /api/* returns HTTP 403:

# .env (Compose)
DSH_TRUSTED_HOSTS=dsh.example.com
DSH_ALLOW_REMOTE_CONFIGURATION=1

# or docker run
docker run --rm -it \
  -p 127.0.0.1:3080:3080 \
  -e DSH_TRUSTED_HOSTS=dsh.example.com \
  -e DSH_ALLOW_REMOTE_CONFIGURATION=1 \
  -v deepseek-harness-home:/home/node/.dsh \
  -v "$PWD:/workspace" \
  alliot/deepseek-harness:0.2.0-rc.2

Values must be comma-separated host or host:port entries — no https:// scheme, paths, or wildcards. A hostname without a port matches any port on that host; for example DSH_TRUSTED_HOSTS=dsh.example.com,192.168.1.20. The reverse proxy should preserve the original Host header, e.g. Nginx uses proxy_set_header Host $host;.

DSH_TRUSTED_HOSTS is a DNS rebinding allowlist, not an authentication mechanism. By default, this image restricts settings, credentials, and model discovery endpoints to loopback; therefore with only DSH_TRUSTED_HOSTS set, the remote "Models" page shows transport failure for /api/settings/describe: HTTP 403.

DSH_ALLOW_REMOTE_CONFIGURATION=1 is an explicit compatibility switch provided by this image. It only opens settings/describe|update|replace|mutate, credentials/describe|set|unset, and llm/discoverModels to trusted hosts; native operations such as opening config files, opening host paths, and directory selection remain loopback-only. The entrypoint rejects the combination of enabling the switch without any trusted hosts configured. Note that since upstream 0.2.0 every web request (loopback or trusted host) additionally requires browser authentication: find the dsh web: http://127.0.0.1:3081/?token=... line in the container logs (3081 is the in-container port; use your published host port when visiting) and open that link in the browser to mint a session cookie first.

Once enabled, the reverse proxy must authenticate users first; otherwise anyone who can reach the domain can read or modify the Harness configuration, write credentials, and have the host issue model discovery requests. Even when deployed in a container, the Agent still has full access to the mounted workspace and any network resources the container can reach.

Sandbox and Workspace Write (bwrap error)

When an operation needs to write files, you may see an error like:

sandbox mode "workspace-write" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined ... bwrap: Can't mount proc on /newroot/proc: Operation not permitted

Cause: the DSH read-only / workspace-write modes rely on a process-level sandbox (bubblewrap/bwrap on Linux). This image intentionally makes the container itself the isolation boundary — a read_only root filesystem, cap_drop: ALL, and no-new-privileges — and does not install bwrap. bwrap needs to mount a fresh procfs inside a new user/PID namespace, which requires privileges the container deliberately drops, so mounting /proc returns Operation not permitted.

Fix: run the Agent in danger-full-access inside the container — the container is the sandbox, so there is no need to nest bwrap. Edit the persistent /home/node/.dsh/settings.yaml (the dsh-home volume) to set the default permission preset:

permissionPresets:
  defaultPreset: danger-full-access

New sessions then start in danger-full-access and never invoke bwrap. You can also switch it in the Web UI permission selector, or run /permissionPresets danger-full-access in a session (affects the current session only).

danger-full-access means the Agent is no longer confined by a process-level file sandbox inside the container; isolation is provided entirely by the container boundary. Only mount directories you are willing to let the Agent read and write. Do not relax the container hardening (for example seccomp=unconfined or CAP_SYS_ADMIN) just to make bwrap work — that would weaken the real isolation the container provides.

Publishing to GHCR and Docker Hub

The workflow is in .github/workflows/docker-publish.yml and behaves as follows:

  • Pull Request: builds the linux/amd64 image, verifies the CLI version and web health; after completing browser authentication (launch token exchanged for a session cookie), confirms remote configuration returns 403 by default, settings/describe returns 200 after explicit opt-in, untrusted hosts still return 403, and native host operations are still not remotely callable; keeps issuing real HTTP requests; then verifies linux/amd64 and linux/arm64 builds without pushing.
  • Push to main/master: after verification, pushes the branch tag, upstream version tag, and commit SHA tag.
  • v* Git tag: additionally generates SemVer tags and latest.
  • Manual run: pushes only when publish is set to true.
  • GHCR is always published; when Docker Hub credentials are present, the same manifest is published to Docker Hub. Missing Docker Hub credentials do not block GHCR builds and publishing.

Configure in the GitHub repository:

Type Name Purpose
Secret DOCKERHUB_USERNAME Docker Hub username; set to alliot in this repository.
Secret DOCKERHUB_TOKEN Docker Hub access token — do not use your account password.
Variable (optional) DOCKERHUB_REPOSITORY Docker Hub repository name; defaults to deepseek-harness.

This repository publishes to:

  • GHCR: ghcr.io/alliottech/deepseek-harness
  • Docker Hub: alliot/deepseek-harness

GHCR uses the GITHUB_TOKEN GitHub provides automatically, and the workflow already declares packages: write. After the first release, you can set the package visibility to Public and link the package to this repository in the GitHub Package settings. The Docker Hub token needs Read & Write permission on alliot/deepseek-harness.

Container releases use SemVer. The upstream pre-release version stays in the prefix and the last digit represents this repository's container wrapping revision, for example:

git tag -a v0.2.0-rc.2 -m "DeepSeek Harness 0.2.0-rc.2 container release"
git push origin v0.2.0-rc.2

When updating the upstream version, update package.json, package-lock.json, and the DSH_VERSION default in the Dockerfile at the same time, then complete the local image health check. Dependabot is configured to track npm, base image, and GitHub Actions updates.

Customizing the Toolchain

The default image ships only the Node.js required by DeepSeek Harness, plus bash, Git, OpenSSH client, and ripgrep. Derive your own image as your project requires:

FROM ghcr.io/alliottech/deepseek-harness:0.2.0-rc.2

USER root
RUN apt-get update \
    && apt-get install -y --no-install-recommends python3 \
    && rm -rf /var/lib/apt/lists/*
USER node

License and Attribution

The container wrapping code in this repository is licensed under the MIT License. DeepSeek Harness itself is developed by DeepSeek AI and licensed under the MIT License; this project is not an official Docker distribution by DeepSeek AI.