69 KiB
DSH-Docker Complete Guide
English | 中文 | Quick start
This guide expands the root README into a complete deployment, operation, recovery, security, release, and development reference.
Contents
- Deployment
- Configuration
- Platform Architecture
- Gateway
- Outbound Proxy
- Online Updates
- System Plugins
- System Skills
- Standalone Recovery Tools
- Logs
- Update Channels and Rollback
- Trust and Recovery
- Security Model
- Release Automation
- Build and Test
Deployment
Image Variants
| Variant | Docker Hub rolling tag | Docker Hub DSH-version tag | Contents |
|---|---|---|---|
| Standard | latest |
<version> |
DSH and normal runtime utilities |
| Devtools | latest-devtools |
<version>-devtools |
Standard image plus development tools |
Use the Standard image for ordinary deployments. It includes the minimal compiler toolchain needed by native DSH plugin dependencies. The Devtools image adds broader diagnostics, editors, and other development utilities while using the same persistent data layout.
Docker Hub publishes both variants. GHCR is a Standard-image backup only: ghcr.io/yjrszcq/dsh-docker uses the Environment tags latest, x.x.x, x.x, and x, plus the DSH lookup tag dsh-x.x.x-rc.x; it never publishes Devtools tags.
Before You Start
- Directory permissions: Bind-mounted DSH data, platform data, and workspace directories must be writable by UID/GID
1000:1000. Keep both data directories when replacing or upgrading a container. - Port exposure: Bind
127.0.0.1:3080:3080for host-only access.3080:3080or0.0.0.0:3080:3080publishes DSH on every host interface. - Remote access: Set
DSH_TRUSTED_HOSTSto the exact browser-facing IP addresses or domains, initialize the local administrator, and terminate HTTPS outside the container. - Agent root authority: DSH/Agent passwordless sudo is disabled by default. Enable it only when unrestricted root access is intentional: set
DSH_SUDO_ENABLED=truewith repository Compose, addgroup_add: [dsh-sudo-true]to custom Compose, or add--group-add dsh-sudo-truetodocker run. - Management root authority: Disabling Agent sudo does not restrict the standalone DSH Management Console. Its container terminal and file manager intentionally run as root and require authentication and a trusted network boundary.
- Recovery access:
/_dsh_platform/console/remains available while DSH is stopped or cannot start. It requires a separate authenticated Management Session; lost credentials are recovered only from an interactive Root console.
Minimal Bind-Mount Compose
Create transparent, host-visible persistent directories:
mkdir -p data/dsh data/platform workspace
Create docker-compose.yaml:
services:
deepseek-harness:
image: szcq/deepseek-harness:latest
container_name: deepseek-harness
restart: unless-stopped
ports:
- "127.0.0.1:3080:3080"
volumes:
- ./data/dsh:/data/dsh
- ./data/platform:/data/platform
- ./workspace:/workspace
Then start the container:
docker compose up -d
If a bind mount is not writable, fix the host directory ownership before retrying:
sudo chown -R 1000:1000 data workspace
Equivalent Docker Run
The same bind-mount deployment can be started without Compose:
docker run -d \
--name deepseek-harness \
--restart unless-stopped \
-p 127.0.0.1:3080:3080 \
-v "$(pwd)/data/platform:/data/platform" \
-v "$(pwd)/data/dsh:/data/dsh" \
-v "$(pwd)/workspace:/workspace" \
szcq/deepseek-harness:latest
Open http://127.0.0.1:3080.
Repository Compose
The checked-in docker-compose.yaml is the configurable production-oriented alternative. It uses named volumes dsh-data and dsh-platform, bind-mounts the workspace selected by DSH_WORKSPACE, and reads the settings documented in Compose Variables. Start from the supplied environment template:
cp .env.example .env
docker compose up -d
Named volumes survive ordinary container replacement and docker compose down, but they are not visible as normal project directories. Include both complete volumes in Docker-aware backups. docker compose down -v deletes them and therefore deletes persistent DSH and platform data.
Configuration
Compose Variables
| Variable | Default | Description |
|---|---|---|
DSH_IMAGE_TAG |
latest |
szcq/deepseek-harness image tag |
DSH_LISTEN_ADDRESS |
127.0.0.1 |
Host address used for port publication |
DSH_PORT |
3080 |
Published host port |
DSH_WORKSPACE |
./workspace |
Host directory mounted at /workspace |
DSH_SUDO_ENABLED |
false |
Add unrestricted passwordless sudo to DSH and its Agent; true or false |
These values describe the checked-in .env.example. Compose stores DSH and platform data in named volumes and bind-mounts DSH_WORKSPACE at /workspace.
Container Variables
| Variable | Default | Description |
|---|---|---|
DSH_PLATFORM_DATA |
/data/platform |
Platform state, managed assets, snapshots, and logs |
DSH_HOME |
/data/dsh |
DSH configuration and data directory |
DSH_DEFAULT_WORKSPACE |
/workspace |
Default directory for directory pickers and standalone file management; must be an accessible absolute directory |
DSH_TELEMETRY_DISABLED |
true |
Disable upstream telemetry; true or false |
DSH_TRUSTED_HOSTS |
Empty | Comma-separated external host or host:port authorities |
DSH_PROXY_POLYFILL |
true |
Inject the guarded crypto.randomUUID compatibility shim |
DSH_LOG_MAX_BYTES |
104857600 |
Aggregate platform JSONL log budget |
DSH_LOG_RETENTION_DAYS |
14 |
Platform log retention |
DSH_ACTIVATION_TIMEOUT_SECONDS |
60 |
Update activation health deadline |
DSH_EXPERIMENTAL_PROBATION_SECONDS |
120 |
Experimental Runtime observation period before commit |
DSH_TRUSTED_HOSTS accepts:
- Empty: loopback Hosts only.
- One value: loopback plus that host; a value without a port matches any port.
- Comma-separated values: every listed authority.
*: any Host. Origin, Fetch Metadata, and optional password checks remain enabled.
Values must not contain a scheme, path, credentials, or subdomain wildcard. Valid examples include dsh.example.com, dsh.example.com:8443, 192.168.1.100, and [fd00::1]:3080.
Workspace Behavior
DSH_DEFAULT_WORKSPACE selects the initial directory for directory pickers and standalone file management. It is not a filesystem sandbox. Standalone file operations run through the root Maintenance Broker, while DSH validates workspace access for its own node process when the Environment starts.
The image implements this with an exact-match compiled-output patch. The patch must match exactly once, so an incompatible upstream release fails the build instead of modifying an unintended location.
Platform Architecture
Docker Image
│
├── System Runtime + tini
├── Pinned Recovery Root public key
└── Signed Image Seed
├── Image Inventory
├── Bootstrap Record
└── Deployment Record
│
▼
Stage-0 (trust and privilege root)
│
├── Trust API
│ ├── Recovery-signed keyring
│ ├── Release metadata / receipt verification
│ └── trusted Object Store import
├── Image / Store Reference resolution
├── Bootstrap current / previous selection and rollback
├── Maintenance Broker (root file and terminal operations)
└── signal forwarding
│
▼
Bootstrap Runtime (current / previous)
│
├── DSH Lifecycle Broker (one supervised Web instance)
├── Control Plane (persistent)
│ ├── gateway 0.0.0.0:3080
│ ├── outbound-proxy 127.0.0.1:17891-17898
│ ├── management + DSH Management Console Unix socket
│ │ └── in-process modules
│ │ ├── updater / patch-manager
│ │ ├── plugin-manager / skill-manager
│ │ └── log-manager / file-manager
│ └── platform-recovery oneshot hook
│
└── Container Environment (reloadable)
├── dsh-runtime 127.0.0.1:3079
└── Environment Resources
├── Patches
├── System Plugins
└── System Skills ──verified materialization──▶ Runtime Skill view
Verified Pristine DSH + Complete Patch Set
│
▼
Immutable Runtime DSH
+
Environment view + System Plugin overlay
+
receipts / snapshot references
│
▼
Content-addressed Deployment Record
│
candidate health check
│
▼
Atomic current / previous slots
Stage-0 is the only writer of trust state and owns Bootstrap A/B selection and rollback together with the root Maintenance Broker, independently of ordinary node processes. Bootstrap owns the DSH Lifecycle Broker, component supervision, health checks, and Environment failure recovery. Management is a persistent Control Plane service; its managers are in-process modules, not independently versioned components or listeners. Initial immutable versions run directly from the read-only image seed through validated Image References; only online update outputs are materialized in the platform data volume. Bootstrap supervises the persistent Control Plane separately from the reloadable Environment, so replacing, suspending, or restarting DSH does not stop Gateway, Management, or DSH Management Console.
On a normal cold start, Bootstrap completes update and User Plugin recovery preflight, then starts the supervised DSH Runtime concurrently with the independent Control Plane startup group. Gateway can serve its authenticated holding and recovery surfaces before DSH is usable. DSH listening is not public readiness: Bootstrap publishes the current lifecycle generation only after DSH resources and Plugin inventory are verified and the complete Control Plane, access classification, Management, and recovery state are ready. Gateway admits DSH HTTP and WebSocket traffic only for that published generation, so neither a Gateway-first nor a DSH-first startup race can expose a half-ready Runtime or bypass Gateway.
System Skill sources belong to the Environment and are packaged as an Artifact in its signed manifest. Bootstrap verifies and materializes the catalog from the selected Environment, then publishes /run/dsh-platform/views/skills. Because the Environment directory is part of the Deployment Record, Skill content follows the same atomic activation and rollback boundary without making content-only edits change Bootstrap.
The source tree follows the same boundary:
container/platform/: Stage-0, Bootstrap, shared contracts, and release tools.container/control-plane/services/: persistent Gateway and Management processes.container/control-plane/hooks/: supervised one-shot recovery work.container/control-plane/modules/: updater, logging, patch, System Plugin, and System Skill logic.container/environment/resources/skills/: Environment-owned System Skill catalogs and instruction trees, packaged as a signed Environment Artifact for trusted runtime management.container/environment/: the complete Container Environment source, including workloads andresources/{patches,plugins,skills}.scripts/: repository maintenance commands such as Environment version synchronization; not included in the container runtime.
Platform Data and Runtime Resolution
Persistent state and assets are deliberately separate from per-start runtime views:
/data/platform/
├── state/{trust,bootstrap,deployments,updater,management}
├── store/{objects,bootstrap,environments,pristine,runtimes,system-plugins,snapshots}
├── cache/{downloads,npm}
└── logs
/run/dsh-platform/
├── stage0-trust.sock
├── bootstrap.sock
├── dsh-lifecycle.sock
├── management.sock
├── maintenance.sock
├── gateway-access.sock
├── recovery.sock
├── deployments/
├── system-plugin-views/
├── deployment
└── views/{bootstrap,environment,runtime,system-plugins,skills}
state is authoritative selection, trust, and transaction state. store contains immutable Managed assets and rollback material and is reclaimed only when no slot, transaction, Hold, receipt, or snapshot refers to it. cache is disposable: downloads holds untrusted transfer data and npm reuses integrity-checked dependency downloads while constructing future Pristine DSH versions. /run/dsh-platform is rebuilt on every container start and must never be backed up or mounted as persistent data. /data/dsh remains a separate user-data volume.
Runtime, Environment, and System Plugins form one content-addressed Deployment Record. Bootstrap resolves that complete record into one candidate view, starts it, checks health, and commits the current/previous slots atomically. A partial combination is never selected after restart.
Patches are mandatory Deployment content, not user-selectable resources. Before every DSH start, recovery, reload, or single-service restart, Bootstrap checks each current Environment Patch Artifact's SHA-256 and size and runs the Patch-owned applied-result verifier. A candidate DSH is not started when verification fails.
When the current Deployment assets cannot resolve, Patch verification fails, or DSH fails to start, Bootstrap temporarily selects previous when one exists. It swaps the slots through a resumable journal only after previous passes the same Patch and health checks and its receipts activate. If previous also fails, no slot is committed and the Control Plane enters recovery mode. Unparseable Records and trust conflicts never trigger an automatic downgrade.
The image contains an immutable Bootstrap and Deployment inventory. With no platform state, these assets run directly from the image without copying the Seed tree. A newer signed Stable image becomes the baseline only after health checks. A Managed deployment with a higher target sequence remains current and reports that the image is behind; an older image never downgrades it. Equal sequences must describe identical content, otherwise startup refuses the conflict. An Experimental DSH ahead of Stable is preserved while the platform reconciles the formal Environment according to the update state machine.
Consequently, pulling a newer image still matters: when its signed target sequence is newer than the current Stable deployment, the container advances to that image baseline. When an online update is already newer, the image instead provides a verified fallback without overwriting current state.
This pre-release layout is intentionally not migrated from older /data/platform layouts. Stage-0 refuses an old volume with an actionable error. Clear only the platform volume before starting the new image; never delete /data/dsh as part of that reset.
For routine backups, preserve /data/dsh and /data/platform/state. Compose stores them in the dsh-data and dsh-platform named volumes. To retain exact local rollback points, also preserve /data/platform/store, especially snapshots. Backing up both complete volumes is the simplest safe policy. /data/platform/cache and /run/dsh-platform do not need backup.
Gateway
The Gateway validates external Host, Origin, and Fetch Metadata and gates every browser surface through the isolated Access Manager. It proxies the fixed /_dsh_platform/console/ and bounded management API routes to Management. Other HTTP, SSE, and WebSocket traffic goes to DSH with loopback Host and Origin values. Before forwarding to DSH, Gateway also removes external Forwarded, every X-Forwarded-*, and X-Real-IP header. An outer proxy such as OpenResty therefore cannot make same-origin DSH plugins mistake the trusted Gateway hop for an untrusted proxy. Management routes retain those forwarding headers, and the original external request still passes every Gateway security check.
Official DSH classifies the browser from its public hostname and can disable Host-backed settings on non-loopback pages. An exact-match patch marks browsers admitted by this Gateway as loopback, matching the authority sent upstream. No upstream server-side privileged API implementation is patched.
Remote Deployment Examples
Remote publication requires both an external bind address and an explicit trusted-host allowlist. Replace its values before use:
services:
deepseek-harness:
image: szcq/deepseek-harness:latest
container_name: deepseek-harness
restart: unless-stopped
ports:
- "3080:3080"
environment:
DSH_TRUSTED_HOSTS: "192.168.1.100,dsh.example.com"
volumes:
- ./data/dsh:/data/dsh
- ./data/platform:/data/platform
- ./workspace:/workspace
The equivalent command without Compose is:
docker run -d \
--name deepseek-harness \
--restart unless-stopped \
-p 3080:3080 \
-e 'DSH_TRUSTED_HOSTS=192.168.1.100,dsh.example.com' \
-v "$(pwd)/data/platform:/data/platform" \
-v "$(pwd)/data/dsh:/data/dsh" \
-v "$(pwd)/workspace:/workspace" \
szcq/deepseek-harness:latest
A reverse proxy must preserve the original Host header, and remote deployments must terminate TLS outside this container. Restrict the published address with a host firewall when only selected clients should connect. On first access, create the local administrator before admitting other clients.
Local Administrator Authentication
The Access Manager owns one durable local administrator account under /data/platform/state/access. A new installation blocks DSH, API, WebSocket, and Management routes until the browser registers a username and main password. Concurrent registration converges to one account. Damaged initialized state enters recovery-required and never silently creates a replacement account.
A successful DSH login creates an HttpOnly, SameSite DSH Session. It does not authorize the complete Management API. Opening the Management Console consumes a one-time handoff and creates a separate Management Session bound to its Origin and source DSH Session. Direct compatibility or isolated-Origin access always returns to the verified DSH Origin for the main login first; an enabled Management console password is a second layer, never a replacement for the main login. Browser forms only transport values; the Access Manager performs authentication and credential decisions. Authentication Settings changes the username without a current password, while main-password and Management-console-password changes require the current main password; the old Management console password is not requested. Resetting or disabling that password revokes existing Management Sessions. DSH logout, expiry, or credential invalidation transitively invalidates linked Management Sessions. Authentication Settings can independently revoke either session class, and changing credentials invalidates sessions through the credential version.
The Access Manager counts password failures per browser source. The fifth consecutive failure starts a 30-second retry wait; each later admitted failure doubles it to a 15-minute maximum. Each source also has fixed rolling limits of 12 failures per hour and 24 per 24 hours. Across all sources, the whole DSH instance has a wider flood limit of 20 failures per minute, 60 per hour, and 120 per 24 hours. A successful password check clears that source's consecutive and rolling history but not instance-wide history. Gateway renders the backend error and remaining whole seconds but never makes the admission decision. Access Manager restart clears these in-memory counters.
When DSH is in a classified stopped, starting, recovering, or failed state, a top-level browser navigation receives a generic Gateway holding page before authentication. It exposes no runtime error detail and links to Management for recovery. Non-page, API, and WebSocket requests continue to return the classified service failure instead of receiving HTML. Once DSH is ready, normal page access still requires a valid DSH Session.
The default compatibility mode serves Management at /_dsh_platform/console/ on port 3080. Optional strong isolation serves Management from a separate container entry with no DSH upstream: a public separate origin uses the exact public Origin, while a local separate origin uses only the host port mapped to that entry (default 3081, configurable to any valid port). The platform verifies the exact candidate entry and instance before switching, then revokes prior Management sessions. The supplied Compose does not publish the separate entry unless the operator explicitly maps or reverse-proxies it. When DSH can obtain container Root, the UI honestly reports that process-level isolation is ineffective and locks this mode transition; Origin separation still protects browser sessions from same-origin DSH client plugins.
Every privileged Management or Maintenance operation is authorized again at its final Unix-socket execution point with a short-lived, single-use capability bound to method, path, session, CSRF, credential version, and access version. Direct socket access cannot substitute for a browser session. Authentication Settings groups sessions by the originating DSH browser login and shows the browser, Gateway peer IP, sign-in time, and last activity. Signing out a listed device atomically revokes its DSH Session and every Management Session derived from it. The DSH-side Platform Management and Settings Document Editor use a fixed restricted Plugin API through the authenticated DSH Session; it cannot reach files, the Root terminal, User Plugin recovery, authentication settings, or complete Management capabilities.
Administrator recovery, session clearing, and retry-limit clearing are Root-only interactive TTY operations. Passwords are read with echo disabled and are rejected from arguments or pipes:
Use the docker exec form below from a host terminal; in the Management Console Root terminal, run the trailing dsh-platform ... command directly.
docker exec -it --user root deepseek-harness dsh-platform access status
docker exec -it --user root deepseek-harness dsh-platform access reset
docker exec -it --user root deepseek-harness dsh-platform access set-username
docker exec -it --user root deepseek-harness dsh-platform access reset-password
docker exec -it --user root deepseek-harness dsh-platform access reset-management-password
docker exec -it --user root deepseek-harness dsh-platform access disable-management-password
docker exec -it --user root deepseek-harness dsh-platform access disable-two-factor
docker exec -it --user root deepseek-harness dsh-platform access clear-sessions
docker exec -it --user root deepseek-harness dsh-platform access clear-sessions --management-only
docker exec -it --user root deepseek-harness dsh-platform access generate-key
docker exec -it --user root deepseek-harness dsh-platform access clear-retry
docker exec -it --user root deepseek-harness dsh-platform access clear-retry --global-only
docker exec -it --user root deepseek-harness dsh-platform access clear-retry --main-password
docker exec -it --user root deepseek-harness dsh-platform access clear-retry --management-password
docker exec -it --user root deepseek-harness dsh-platform access clear-retry --two-factor
access reset is the recommended combined recovery command. It independently asks whether to change the username and main password. When a Management console password is enabled, y replaces it, the default n preserves it, and d disables it. When two-factor authentication is enabled, a separate y/[n] prompt can disable it. Nothing is saved until every selected prompt is complete, so cancelling or losing the TTY halfway through does not partially update the account. Yes/no recovery prompts accept only y or n and show their default in brackets. The narrower interactive commands remain available for single-credential recovery. Session invalidation follows the selected changes: resetting the main password clears all sessions, setting or resetting the Management console password clears Management sessions, and disabling either optional layer keeps existing sessions.
access status is read-only and reports the current account, initialization state, and retry state.
access clear-retry asks Clear all administrator login retry limits? y/[n]:. Confirming clears every browser-source exponential wait, all browser rolling windows, and all instance-wide failed-attempt windows. With --global-only, the CLI instead asks Clear instance-wide administrator login rate limits? y/[n]: and clears only the instance-wide windows; browser waits and source windows remain active. Neither form changes credentials or browser sessions.
access clear-retry accepts --main-password, --management-password, or --two-factor to select a credential class; 2FA clearing leaves the fixed 10-second code wait active. access clear-sessions clears all DSH and Management sessions, while --management-only preserves DSH sessions. Disabling 2FA or the Management password does not log out existing sessions; resetting the main password clears all sessions, and setting or resetting the Management password clears all Management sessions.
An empty platform volume always enters normal first-use registration and needs no key; legacy password environment variables alone cannot classify an empty volume as an old deployment. Persisted pre-account deployments enter migration-required, while a missing or damaged initialized account enters recovery-required. For either state, run docker exec -it --user root deepseek-harness dsh-platform access generate-key from a Root TTY to issue a single-use authentication reset key valid for ten minutes, then create the replacement account in the browser. A new key immediately invalidates the previous key. Legacy environment passwords are only supplemental evidence for a persisted deployment: their values are stripped before Bootstrap and DSH start and are never retained as a hidden login bypass.
Browser Compatibility
Gateway injects a feature-detected crypto.randomUUID polyfill into HTML by default. It runs only when needed, uses crypto.getRandomValues, and never falls back to Math.random. Set DSH_PROXY_POLYFILL=false when clients or a future DSH version no longer need it.
Modified HTML uses Cache-Control: no-cache and drops invalid upstream validators. Unmodified assets retain upstream caching behavior.
Outbound Proxy
The Proxy tab in the standalone Management Console and the DSH Platform Management settings configures an existing HTTP or SOCKS5 proxy. This is outbound routing, not another public proxy service. Gateway remains the inbound browser reverse proxy and starts first so holding pages are available while Outbound Proxy and the rest of the Control Plane start. No outbound-proxy port is published by Docker.
The master switch and each source switch are independent. Managed traffic is divided into updates, platform components, DSH core, DSH plugins, Agent network operations, and the Management container terminal. Every remote model Provider that DSH identifies in llm/stream can be configured independently: turning its proxy switch off selects direct access, while turning it on uses a dedicated proxy route. Loopback and other local Providers are always direct. If a future client cannot carry a stable Provider identity end to end, the UI shows a read-only Follow DSH label and an explanation; its switch selects direct access when off and the shared DSH policy when on.
| Loopback port | Scope |
|---|---|
17891 |
update checks, metadata, and Artifact downloads |
17892 |
platform components and DSH Docker System Plugins |
17893 |
distinguishable DSH core requests |
17894 |
distinguishable DSH plugin requests |
17895 |
Agent tools, commands, and managed Agent shells |
17896 |
Management container terminal |
17897 |
independently routed model Providers |
17898 |
shared DSH fallback traffic |
HTTP and WebSocket requests use proxy absolute form; HTTPS and WSS use CONNECT without TLS interception. The external endpoint may use HTTP Basic authentication or SOCKS5 username/password authentication. SOCKS5 can resolve names locally or through the proxy. Existing connections retain their captured policy, while a configuration change affects new connections immediately.
Direct Rules and Environment Compatibility
The platform always bypasses loopback addresses and internal control paths. The editable direct-rule field accepts exact hosts, leading-dot domain suffixes such as .google.com, IP addresses, optional ports, and CIDR networks; *.google.com is not accepted. Host and domain rules become user NO_PROXY, while CIDR rules remain centralized Outbound Proxy bypass rules. Platform-required entries cannot be removed and are shown in the Built-in rules dialog.
HTTP_PROXY, HTTPS_PROXY, and merged NO_PROXY values point managed processes at their fixed scope entry. Optional ALL_PROXY injection is disabled by default and is only a compatibility aid; curl, Git, npm, pnpm, Conda, language SDKs, and other tools decide whether and how they honor it. Host-side docker exec shells are outside platform routing unless the user configures those commands explicitly.
If a proxy runs on the Docker host at 127.0.0.1:7890, the same address inside a container refers to the container itself. Determine the active bridge gateway instead of hard-coding it:
docker exec deepseek-harness sh -lc "ip route | awk '/default/ { print \$3; exit }'"
The default bridge often reports 172.17.0.1, while Compose networks commonly use another address. The host proxy must listen on that bridge address or 0.0.0.0, with a firewall limiting access. The UI never rewrites 127.0.0.1 automatically.
Credentials, Tests, and Failures
Proxy credentials are write-only. They are stored under /data/platform/state/proxy by the dedicated dsh-proxy identity and are not returned by APIs, injected into DSH, or written to platform logs. Management forwards updates through the Outbound Proxy control protocol; Stage-0 can launch the fixed component but cannot read its configuration or credentials.
Test proxy evaluates the unsaved candidate in phases without replacing the active revision. It checks the proxy endpoint, DNS/TCP/TLS, GitHub/npm dependencies, and supported Provider endpoints without making a billable model request. A failed or cancelled test leaves the current configuration active. Update checks keep their last verified result when a later remote request fails and distinguish that case from a first check that has never succeeded.
Online Updates
Checks and Notifications
/data is the container data namespace. Platform state lives in /data/platform; DSH settings, sessions, credentials, and third-party plugins live in /data/dsh. Keep the two independently mounted volumes.
Automatic checks default to every six hours with jitter and can be disabled or rescheduled from either Management frontend. Checks never download or activate an update. Optional notifications appear only on DSH pages and only after an automatic check; the standalone console never shows an update popup. Opening either Management frontend performs one read-only check in the background, while a manual check refreshes the saved result without notifying. Both frontends open on Maintenance: the standalone console then orders Files, Container terminal, System Plugins, System Skills, User Plugins, User Skills, and Updates; Platform Management inside DSH orders System Plugins, System Skills, Authentication, Proxy, and Updates. Its Authentication tab can revoke the current browser's DSH Session and linked Management Sessions, but cannot edit credentials or obtain complete Management authority. The Management component serves the standalone console at /_dsh_platform/console/ and follows the saved DSH locale when available.
DSH Lifecycle and Runtime Maintenance
The standalone console and dsh-platform start|stop|restart control only dsh-runtime. Bootstrap, Gateway, Management, and the container remain running. An explicit stop lasts until DSH is started again or the container itself restarts. Lifecycle operations are mutually exclusive with update activation, rollback, Runtime reset, and plugin transactions.
The CLI returns a task ID immediately by default. When running from the current DSH session, use asynchronous dsh-platform restart and do not use restart --wait or stop --wait, because stopping DSH also interrupts that session's tool transport. --wait remains appropriate for docker exec, the standalone console terminal, and external automation.
Before each Web Profile launch, Bootstrap issues a one-time token and binds the supervised instance through the internal Broker at /run/dsh-platform/dsh-lifecycle.sock. A manual command or third-party helper that runs dsh web again cannot create a second instance. It submits a formal start task when DSH is stopped; reports the current state and exits while DSH is running, starting, restarting, recovering, or owned by another platform transaction; and directs the user to the standalone console in failed/recovery mode. Tokens and sessions are never written to logs, persistent state, or Deployment Records.
When the supervised DSH receives a first SIGTERM that was not registered by the platform, it asks the Broker for disposition and submits a formal asynchronous restart through Management. The browser can therefore show Restarting DSH, while any detached replacement created in advance remains unable to bypass the single-instance gate. During an explicit Bootstrap stop, restart, switch, or container shutdown, the Broker instead tells DSH to terminate gracefully so no duplicate task is registered. A second SIGTERM, request timeout, or unavailable Control Plane falls back to the original graceful exit and bounded Bootstrap recovery. process.exit(), uncaught exceptions, and SIGKILL remain unexpected exits and are never presented as normal restarts.
Before a registered operation disconnects DSH, open browser pages move to a localized holding page. The page distinguishes starting, stopping, stopped, restarting, unexpected recovery, Runtime switching/recovery, and startup failure, then returns to the original same-origin path after readiness. Gateway readiness requires Bootstrap to publish the current lifecycle generation after both a responsive DSH HTTP upstream and the complete platform lifecycle have left every start, restart, recovery, and switch state. It therefore does not return the browser while DSH is listening but Bootstrap is still completing resource, Plugin inventory, Control Plane, or recovery checks. A brief transport interruption is verified through this combined readiness before navigation. API and WebSocket requests continue receiving 503; these classified lifecycle responses are not logged as upstream failures or recoveries. Unknown proxy failures remain 502 and retain their failure logs.
If a browser requests a plugin bundle during a registered lifecycle transition, Gateway holds the request until DSH is ready. This applies to every DSH-loaded client bundle, including official DSH plugins, bundled System Plugins, and User Plugins. If a bundle request still returns 502 or 503, a network failure reaches the dynamic importer, or the importer raises a structured module-load error, the guard confirms platform readiness and may enter the holding page twice for the same running Deployment. A third structured failure opens Gateway's terminal DSH plugins keep failing to load page with a link to the standalone Management Console; it does not create an unbounded reload loop. Detection uses the failed plugin request or module error URL, never visible page text. The platform log records failure, recovery start, recovery completion, and final failure as browser.plugin-load.failed, browser.plugin-load.recovery.started, browser.plugin-load.recovery.completed, and browser.plugin-load.recovery.failed, including bounded plugin, revision, lifecycle-task, recovery-attempt, and reason fields.
If dsh-runtime exits without a registered platform operation, Bootstrap retries it at most three times with immediate, 2-second, and 5-second delays. Recovery does not run in parallel with an update, rollback, reset, or probation owner. Three failed attempts enter recovery mode while Gateway and the Management Console remain available.
The image HEALTHCHECK probes Gateway control-plane health at 127.0.0.1:3080/_dsh_gateway/health. It does not depend on DSH readiness, so an intentional DSH stop does not make the container unhealthy while authentication and the standalone Management Console remain available.
The standalone console also provides Reset runtime for repairing damaged DSH program or patch bytes. It rebuilds the current Runtime from the verified Pristine DSH and the current Environment's complete Patch Set, verifies that the rebuilt content still matches the current Deployment Record, and only then pauses and restarts DSH. It does not change the DSH or Environment version, update channel, rollback slots, settings, sessions, credentials, or third-party plugins under /data/dsh. If the rebuilt Runtime cannot start, the prior Runtime directory is restored automatically.
System Plugins
The Container Environment currently includes:
| Plugin | Purpose |
|---|---|
@dsh-docker/platform-management |
Adds Platform Management to DSH settings for updates, maintenance, logs, System Plugin, System Skill, and current-browser session controls |
@dsh-docker/settings-navigation |
Keeps the desktop Settings directory independently scrollable and provides directory/detail navigation on narrow screens |
@dsh-docker/settings-document-editor |
Replaces desktop-only configuration-file opening with an optional browser editor for settings.yaml |
The standalone console lists every System Plugin bundled by the current Environment and can install, uninstall, enable, or disable them, including recovery of the platform-management DSH integration. The integration inside DSH shows missing plugins with an Install action and limits installed plugins to enable/disable; it cannot uninstall them. Selections remain a page-local draft until Apply, so refreshing or leaving before Apply discards them. Apply persists each change and normally restarts DSH to activate it. If DSH is already unavailable, or that restart fails after the change commits, the stored selection is retained for the next successful start instead of remaining falsely marked as unapplied. Installation rebuilds and verifies the complete System Plugin Set from the current Deployment's local trusted Environment Artifact against the Deployment Record content hash. It never contacts GitHub or npm, never copies files from a built Runtime, and never reinstalls a missing plugin automatically.
Settings Navigation leaves DSH's native Settings content mounted and changes only navigation presentation after semantically recognizing the official dialog structure. Desktop Settings retain the native two-column layout while the directory scrolls independently. Panels narrower than 640 pixels open on a scrollable directory; selecting an item opens its detail with a back button, preserving the selected section, unsaved content, directory scroll position, and focus across navigation and resizing. Disabling, uninstalling, or failing to recognize the shell restores the unmodified DSH Settings layout.
The optional Settings Document Editor System Plugin replaces DSH's native Open configuration file action in container deployments with a responsive browser editor. It edits only the current $DSH_HOME/settings.yaml, saves atomically, and rejects a save when the file changed after the page loaded. It uses the same restricted DSH-side platform boundary as Platform Management and remains usable without first signing in to the standalone console.
System Skills
The signed Environment includes dsh-docker-operations, an English machine-facing operations guide for the official container environment. Its compact SKILL.md routes the Agent to focused references covering identity and permissions, workspaces and development tools, DSH and extensions, lifecycle and logs, updates and recovery, networking and authentication, and ordered diagnostics. The guide instructs the Agent to answer in the user's language and to use dsh, dsh-platform, their current help, and the Management interfaces before inspecting platform implementation details. It explicitly forbids credential discovery, direct socket calls, manual Trust/Store/Runtime-view mutation, and package-manager environment overrides during ordinary operations.
Bootstrap publishes enabled skills from the selected Environment's verified local catalog into /run/dsh-platform/views/skills, and DSH discovers that fixed root through DSH_BUNDLED_SKILL_DIR. System Skills use id + SHA-256 identity and have no independent release version. Their selection is stored at /data/platform/state/deployments/skills.json; uninstalling removes only the active selection, so the signed Environment copy remains available for offline reinstallation. No management action accepts a URL, path, uploaded body, or client-supplied hash.
The standalone console lists every System Skill supplied by the current Environment and supports install, uninstall, enable, and disable. Platform Management inside DSH can install a missing skill and enable or disable an installed one, but cannot uninstall it. All changes atomically update the stable skill view and are picked up by DSH's native filesystem watcher without restarting DSH. The same state survives container restarts, newly signed skills default to installed and enabled, and skills removed by a newer Environment are pruned. Project and user skills retain DSH's native precedence over bundled skills; disabling a System Skill does not alter either override.
dsh-docker-operations is model-discoverable and can also be invoked explicitly as /dsh-docker-operations. It applies to operating the installed environment, not to developing dsh-docker itself. Only an explicit platform-development or implementation-debugging request permits inspection of /opt/dsh-platform and /run/dsh-platform internals.
Standalone Recovery Tools
User Skill Management
The standalone console's User Skills tab inventories native directory and flat-file skills from $DSH_HOME/skills and $DSH_AGENTS_HOME/skills. It does not scan or modify project-level skills. Each entry shows its source, native entry name, parsed Skill name and description, enabled state, and any metadata error; malformed entries remain visible so they can still be disabled or deleted.
Disabling atomically moves the exact entry into that user root's hidden disabled directory while preserving its contents. Enabling restores it to the same root, and Delete permanently removes only the selected entry. Symbolic links are handled as links and deletion never follows their targets. Every action carries the current inventory revision, conflicts with other managed mutations, and is audited as started, completed, or failed. DSH's native filesystem watcher applies enable and disable changes immediately without restarting DSH. The Platform Management integration inside DSH deliberately does not expose User Skill controls.
User Plugin Recovery
The User Plugins and Container terminal tabs in /_dsh_platform/console/ are provided by Management, not DSH. They remain available when dsh-runtime is stopped or fails during plugin startup. The Platform Management integration inside DSH deliberately does not expose these two recovery tabs.
User Plugin recovery manages only Bundle plugins declared by $DSH_HOME/profiles/web/package.json: a package must be both a dependency and an ordered member of dsh.profile.bundles. Ordinary dependencies and hand-written entries in cordis.patch.yml are never rewritten. Damaged installed metadata remains visible and uninstallable. Names reserved by the verified Environment System Plugin manifest cannot be enabled as User Plugins, regardless of package scope or prefix.
Enable, disable, and uninstall changes are accumulated as a page-local draft. Applying them pauses DSH idempotently, snapshots the complete Web Profile, performs the exact actions, validates the resulting Profile, and restarts only DSH. Refreshing or leaving before Apply discards the draft. A revision conflict returns the latest inventory instead of overwriting concurrent changes. Pre-commit interruption restores the snapshot; after commit, a plugin change is retained even if DSH still fails, so multiple faulty plugins can be removed over consecutive attempts. Installation is intentionally not offered here; use DSH's normal plugin flow or the standalone terminal.
Older DSH releases and third-party plugin tools may remove a package without removing its now-unresolvable entry from dsh.profile.bundles. Before an authorized Web Profile launch, the managed Runtime removes only entries which are no longer dependencies and cannot be resolved from either the Profile or the DSH installation. Built-in bundles, declared dependencies, and still-resolvable local bundles are preserved. The repair is atomic and is recorded in the DSH Runtime log; users should restart with the current image instead of editing the Profile manifest by hand.
The managed Runtime also pins the Web Profile pnpm store to $DSH_HOME/.pnpm-store. When an existing Profile still points at a store from an older image, such as /workspace/.pnpm-store or a previous user home, startup copies reusable store content and asks the image's pinned pnpm to rebuild links from the lockfile. The original workspace configuration and node_modules are restored if migration fails or is interrupted. Public dsh commands invoked from a Root container shell are automatically run as the node identity so plugin files do not become Root-owned; dsh-platform and the Management Console terminal remain Root maintenance tools. Do not delete /data/dsh, edit .modules.yaml, or chmod Root package directories to work around a store mismatch.
Container Terminal
The Container terminal tab uses a constrained Maintenance Broker inside Stage-0 to start a real interactive /bin/bash as root. It starts in /root with HOME=/root, and receives DSH_HOME, PATH, and proxy variables. DSH_DEFAULT_WORKSPACE applies to the file manager and ordinary workspace selection, not to this Root maintenance shell. DSH_SUDO_ENABLED controls only DSH/Agent sudo access; it does not reduce this terminal's administrator privileges. Restarting only DSH does not terminate the terminal. A browser refresh or brief disconnect can reattach for 30 seconds and redraw up to 256 KiB of recent output; explicitly closing the session, stopping Stage-0, or stopping the container terminates it. Platform logs record session lifecycle only, never terminal input, output, command history, or the complete environment.
File Management
The standalone DSH Management Console Files tab also remains available while DSH is stopped, fails to start, or is in recovery mode. The Platform Management plugin inside DSH deliberately does not expose this tab. Its initial directory comes from DSH_DEFAULT_WORKSPACE; shortcuts use DSH_DEFAULT_WORKSPACE, DSH_HOME, DSH_PLATFORM_DATA, and /, with duplicates removed. File operations run through the Maintenance Broker as root. They can repair files that the ordinary Management/DSH identity cannot write, while still respecting read-only mounts and the platform-managed-path mutation lease. Host bind mounts backed by Windows, SMB, or another filesystem without Unix metadata may silently ignore chown and chmod; the platform verifies the result and reports failure, so use a Linux/WSL path or named volume with Unix metadata support when ownership and mode changes are required.
Directory inventory supports hidden files, sorting, local filtering, owner/group display, and bounded recursive search. It uses a fixed scrolling viewport and server-backed pages of 50, 100 (default), or 200 items; default name sorting resolves detailed metadata only for the requested page. Directory size is calculated only when requested, as a cancellable read-only task that does not follow symbolic links. The expanded permissions editor changes a file or directory user, group, and octal mode; directory changes can be applied recursively without following symbolic links. Symbolic links are listed, copied, and deleted as links and are not followed recursively. Regular UTF-8 text up to 2 MiB can be edited in the line-number editor. Saves include a revision and return a conflict instead of overwriting changes made by a terminal, Agent, or another page.
Files or complete browser-selected folders can be uploaded while retaining their relative layout, and files can be dropped onto the directory listing to upload them into the current directory. Files stream directly to downloads; directories are temporarily packaged as ZIP and the temporary archive is removed after completion, failure, or cancellation. Selected files and directories can also create and extract ZIP, 7z, and tar.gz archives. Upload, download, paste, archive, extraction, and permanent deletion share one visible FIFO queue. Only one conflicting job runs at a time; queued and running jobs can be cancelled before their safe commit boundary. Measurable transfer phases show actual bytes, while archive phases without reliable tool telemetry remain explicitly indeterminate instead of inventing a percentage. Refreshing or closing the page aborts browser-owned upload/download streams and drops uploads that have not started; staging data is cleaned. Persistent background tasks are rediscovered after reconnecting.
Copy, move, archive, extraction, and permanent deletion run as persistent background tasks. Move and delete write durable commit boundaries so Management can safely finish them after a restart; an uncommitted operation that cannot be proven idempotent is marked interrupted and retains its source. There is no trash. /, /data, /data/dsh, /data/platform, /workspace, and the active Deployment view root cannot themselves be selected for recursive deletion. Platform-managed paths remain accessible but are clearly marked because updates, restarts, runtime rebuilds, or GC can replace changes and manual edits can damage the active Deployment. Audit logs record paths, operation type, byte counts, duration, and outcome, never file contents.
Both tools use the existing Gateway Host, Origin, Fetch Metadata, and the appropriate DSH or Management Session checks and add no listener. This is a container-root management boundary: anyone admitted to the page can read or modify container data and execute arbitrary shell commands. Expose it only behind the trusted boundary described in Security Model.
Logs
New Platform and DSH log entries are also emitted as source-tagged JSON to container stdout or stderr, so docker logs deepseek-harness shows the complete live operational stream. Both Management interfaces support text, Source, and level filters; processed-entry limits of 100, 250, 500, or 1000; manual refresh; optional automatic scrolling; and JSONL export. Each row starts with a compact summary and expands to the complete structured record, including error stacks, causes, task IDs, and diagnostic fields. Clear view affects only the current browser view and does not delete stored logs. Historical entries are not replayed to stdout at startup. Repeated authentication, 2FA, Management-origin probe, and upstream failures are rate-limited by failure class: the first event is retained, repetitions in the short window are suppressed, and the next retained event includes suppressedCount. Browser-session activity is emitted at most once per session every five minutes; absolute expiry, idle expiry, credential or entry-version invalidation, and source-DSH-session invalidation use distinct reason codes. Root authentication recovery also writes audit records containing the operation, result, and session-revocation counts, but never passwords, reset keys, one-time codes, Cookies, or CSRF tokens. Source-separated JSONL under /data/platform/logs remains the authoritative store and rotates according to DSH_LOG_MAX_BYTES and DSH_LOG_RETENTION_DAYS (100 MiB and 14 days by default).
Update Channels and Rollback
docker exec deepseek-harness dsh-platform status
docker exec deepseek-harness dsh-platform check
docker exec deepseek-harness dsh-platform update --wait
docker exec deepseek-harness dsh-platform stop --wait
docker exec deepseek-harness dsh-platform start --wait
docker exec deepseek-harness dsh-platform restart --wait
docker exec deepseek-harness dsh-platform channel experimental
docker exec deepseek-harness dsh-platform retry
docker exec deepseek-harness dsh-platform logs --source updater
docker exec deepseek-harness dsh-platform rollback
docker exec -it deepseek-harness dsh-platform return-stable
When activating from the current DSH session, start asynchronously with dsh-platform update and record the returned task ID. update --wait is reserved for docker exec, the standalone console terminal, and external automation because activation may switch DSH and interrupt that session's tool transport.
During an update, the standalone Management Console and DSH Platform Management show the overall transaction, stage subitems, and current-stage logs across preparation, download and verification, Runtime construction, and switching and health checks. Downloads, copies, Artifact verification, file processing, and health checks report only measurable bytes, items, files, or services; a phase without a trustworthy total does not invent a precise percentage. Each expanded stage keeps its related logs in a bounded, independently scrolling region so a busy transaction does not continuously lengthen the page. Completed stages collapse by default while the current or failed stage remains open. After a refresh or short disconnection, the interfaces reconstruct the progress and stage history from persistent transaction state and task-correlated JSONL logs.
Ordinary rollback and Return to Stable are available only in the standalone DSH Management Console. The in-DSH Platform Management surface retains checking, updating, channel selection, runtime status, and restart controls, avoiding low-frequency data-recovery actions inside DSH itself. Return to Stable appears only while an Experimental Deployment is active, a verified Stable recovery point exists, and no mutually exclusive transaction is running.
Changing channels modifies only local desired state. Stable converges to the signed supported DSH and Environment. Experimental first converges the complete Deployment to the signed Stable target sequence, even when its visible DSH and Environment versions already match, then offers the newest verified upstream DSH. When current DSH is ahead of Latest Supported, the complete combination is frozen until Stable catches up.
Candidate build failures create a version Hold; incompatible Runtime/Environment combinations create a combination Hold. If npm exposes the selected DSH version before every dependency version is available, materialization automatically retries the transient E404, ETARGET, or ENOVERSIONS failure in fresh install roots with bounded backoff. Only a failure that remains after those retries creates the version Hold, with a bounded registry reason. retry clears the one active Hold or Blocked combination after the registry graph or compatibility issue has been corrected.
Before an Experimental Runtime touches real data, Updater stops dsh-runtime and creates a verified tar snapshot of /data/dsh. It then switches Runtime, checks health, and observes the candidate during probation. Failure or interruption restores Runtime, Environment, System Plugins, receipts, and the snapshot before DSH restarts.
rollback restores the retained previous complete state. Interactive return-stable is available only with a verified pre-Experimental recovery point and may discard data written after the displayed snapshot time.
Trust and Recovery
Stage-0 embeds one offline Recovery Root public key. It reads the public machine channel from the append-only release-channel branch, first verifies a monotonically increasing Recovery-signed keyring, then accepts stable.json only from the keyring's current Release Key. Immutable target files live under targets/<targetSequence>/; the branch root exposes the current signed keyring and Stable pointer. Bootstrap and Environment Artifacts downloaded by Updater stay in /data/platform/cache/downloads until Stage-0 matches them to signed descriptors and imports them into /data/platform/store/objects. Every path later used by the Runtime builder comes from the resulting receipt, never from the untrusted download.
Bootstrap and Updater cannot add a root key, modify keyrings, submit arbitrary expected hashes, or mint receipts. They consume only Stage-0 verification results.
Stable metadata delegates the exact official npm Registry origin, @deepseek-ai/dsh package identity, and accepted Registry signing keys. Updater may inspect npm latest to choose an Experimental version, but both Stable and Experimental submit only that selected version to POST /v1/dsh/ensure. Stage-0 independently fetches the exact version metadata with redirects disabled, verifies the Registry signature over name@version:integrity, derives the canonical tarball URL, downloads with a bounded identity-encoded response, recalculates SHA-512, and issues an official-dsh receipt backed by the trusted object store. Updater cannot submit a package, Registry, URL, integrity, expected hash, candidate document, or tarball path through this operation.
The official DSH ledger is monotonic: same-version repair is allowed only for identical signed content, lower-version import is rejected, and rollback restores the retained previous Runtime/Environment/receipt/snapshot state without downloading an older package. A Release-key or Registry-policy change invalidates staged receipts but does not destroy already active or previous states.
dsh is a dynamic shim which always executes the current verified Runtime. dsh-platform trust status reports accepted trust state. dsh-platform trust reset is console-only: stop Stage-0, mount the platform-data Volume into a one-shot container with dsh-platform as its entrypoint, run trust reset from an interactive TTY, and enter the exact confirmation. This clears accepted state but does not replace the image Recovery Root.
If both current and previous Deployments are unusable, the Control Plane remains available in recovery mode. Restore the exact Deployment shipped by the currently running image from a root, interactive container console:
docker exec -it --user root deepseek-harness dsh-platform recover
The command displays the failed current state, image baseline, and data-compatibility warning, then requires the complete image build ID as confirmation. It is unavailable through Gateway and the Web API. Recovery health-checks the image Deployment before committing it and does not delete /data/dsh; operators must still judge whether existing DSH data is compatible with the older or newer image baseline.
For routine Release Key rotation or compromise, use the offline Recovery private key to sign generation+1: promote the old next key to current, revoke the old current key, and add a new next key. Revocations are cumulative. Only Recovery Root compromise or cryptographic migration requires a new image or explicit trust reset.
Recovery private material must never enter GitHub secrets. CI receives only a signed public keyring bundle and the protected current Release private key.
Security Model
An authenticated DSH Session has full DSH authority. A separate Management Session additionally grants the Container terminal and Files tools root authority. An admitted administrator may read or replace model credentials, execute commands, and access any writable path in the container. The Host allowlist mitigates DNS rebinding; it is not user authentication.
Before exposing the service to untrusted networks, use a strong local administrator password, authenticated reverse proxy, VPN, or another trusted boundary. An SSH tunnel can be combined with loopback-only publication:
ssh -L 3080:127.0.0.1:3080 user@server
Repository Compose disables unrestricted passwordless root access for DSH and its Agent by default. Set DSH_SUDO_ENABLED=true only when that authority is intentional. Do not combine sudo with privileged mode, the Docker socket, or sensitive host mounts unless that authority is intentional.
Release Automation
DSH Upstream Update runs daily at 04:00 UTC (12:00 China Standard Time) and on demand. It compares npm latest with release/supported-target.json, keeps the current Environment, and creates or updates a candidate PR based on and targeting dev. It sends one notification after discovering a new upstream version and creating its candidate PR, then one final notification when full compatibility validation passes or fails. Candidate CI verifies npm integrity, applies the current Environment, runs both project suites, and executes standard and devtools container smoke tests. These jobs have no Release or Recovery credentials; candidates merge into dev, and only a later promotion from dev to main enters the production publication flow. The automated candidate branch is validated only by the reusable job inside DSH Upstream Update, while other PRs run the standalone validation automatically. A maintainer can also run DSH Candidate Validation manually against an already merged branch, tag, or commit before publication. Standalone PR and manual results notify through a workflow_run that neither checks out nor executes candidate code, keeping Secrets out of candidate execution. No notification is sent when no newer version exists.
Publish Supported Platform Target runs when the Supported Target, Environment definition, or official DSH Registry policy changes on main, and also supports approved manual dispatch. Configure a protected production-release GitHub Environment restricted to main with:
DSH_RECOVERY_ROOT_PUBLIC_KEYDSH_KEYRING_JSON_BASE64DSH_KEYRING_SIGNATURE_BASE64DSH_RELEASE_PRIVATE_KEY
The workflow starts the first formal targetSequence at 1 and appends each later signed target to the release-channel branch. It validates the selected npm tarball and binds its npm integrity into Stable metadata, but does not republish a duplicate DSH tarball; Stage-0 imports the official npm copy. A retry of the same source commit and keyring reuses the already-published target instead of consuming another sequence. The Recovery private key has no workflow input.
GitHub Releases describe only the Container Environment. A new Environment publishes v<environment-version> (for example v1.0.0) and marks it Latest. A DSH-only update advances the signed channel and rebuilds images without creating a GitHub Release. Changes to packaged Environment or Bootstrap content, or to release/official-dsh-policy.json, require an Environment version increase; the Environment fingerprint check rejects reuse of an existing version for different content. System Skill content is an Environment Artifact, so a Skill-only edit does not change the Bootstrap package. When Bootstrap code or other packaged Bootstrap content changes, also increment container/platform/bootstrap/VERSION; release validation rejects reusing the previous Bootstrap version.
When preparing an Environment release, run this from the repository root:
./scripts/bump-environment.sh <new-environment-version>
The script updates only the two authoritative machine-readable versions in container/environment/definition.json and release/supported-target.json. It rejects invalid SemVer, version rollback, or an existing mismatch. It does not change documentation or the DSH version. Do not run it for a DSH-only update; the upstream workflow advances only the DSH field in the Supported Target.
Each Environment Release uploads only one custom asset, environment-release.json, to prevent one version from being rebound to different Environment content. Online updates fetch manifests and artifacts from the immutable release-channel and have Stage-0 verify each item; they do not depend on GitHub Release assets at all. With GitHub's two automatically generated source archives, the Release page normally shows three assets.
Publish Container Images runs as a separate Actions run after Publish Supported Platform Target completes successfully. It is protected by the separate production-image Environment, so the Release workflow itself never accesses the image environment. Put DSH_RECOVERY_ROOT_PUBLIC_KEY in that Environment and DOCKER_TOKEN in repository or organization Actions Secrets. The image job has no Release private key; it resolves the exact matching immutable channel commit from the append-only release-channel history using the upstream run's source commit. Grant the workflow packages: write; it logs in to GHCR with GITHUB_TOKEN.
The Standard multi-architecture image is built once and pushed to both registries. Docker Hub receives <dsh-version> and latest; its separately tested Devtools image receives <dsh-version>-devtools and latest-devtools. GHCR receives only the Standard image under latest, full/minor/major Environment tags, and dsh-<dsh-version>. A DSH-only update moves the current Environment tags without publishing a GitHub Release. An Environment-only update intentionally replaces the existing Docker Hub DSH tags with a new image digest and publishes the new Environment hierarchy on GHCR.
Repository or organization Secrets GOTIFY_URL and GOTIFY_TOKEN are passed explicitly to the reusable Gotify workflow. One result-notification workflow handles only final success or failure for formal target publication and manual candidate PR validation; each event can match exactly one notification job. After the signed target and any required Environment Release are published, it reports that image publication has started and may be waiting for production-image approval; notification service availability cannot affect the formal publication result or block the image trigger. The image workflow also sends exactly one final result: success after both image variants are published and verified, or a failure notification with the Actions run link if any step fails.
Build and Test
Build the standard image:
docker build -t deepseek-harness:local .
Build a specific official package for local development, or build the devtools variant:
DSH_VERSION="$(jq -r .latestSupportedDsh release/supported-target.json)"
docker build --build-arg "DSH_VERSION=$DSH_VERSION" -t "deepseek-harness:$DSH_VERSION" .
docker build --build-arg INSTALL_DEVTOOLS=true -t deepseek-harness:local-devtools .
An arbitrary local DSH_VERSION produces a development-authority inventory with target sequence 0. It cannot become a formal tag or latest. Production workflows build only the reviewed Supported Target from a verified signed channel target, reject the marked non-production trust fixture, and require an offline Recovery-signed public trust bundle.
Run local checks with Node.js 24 and Docker Compose:
npm ci --omit=dev --ignore-scripts --prefix container/control-plane/services/management
npm test --prefix container/control-plane/services/gateway
npm test --prefix container/platform
node container/test/compose-config.mjs
With Docker available, container/test/container-smoke.sh [image] checks managed processes, trust, password flow, persistent Console access, and the loopback-only DSH listener. container/test/devtools-smoke.sh <image> checks the devtools variant.
The standard image includes Node.js 24, pnpm, Python 3 with venv, Git, OpenSSH, curl, jq, ripgrep, optional sudo, and the minimal make/C++ toolchain needed to build native DSH plugin dependencies. Devtools additionally includes broader development headers and tools, Bash completion, network diagnostics, archive and file utilities, Vim, pkg-config, and pinned uv.
Devtools uses uv instead of a shared pre-created Python environment. Use uv run --with requests script.py, or uv sync and uv run in projects. Bare pip and pip3 commands are intentionally absent; python3 -m venv remains available. Additional Python versions require an explicit uv python install <version>.