The Philosophical Developer — Chapter 46: Containerizing DeepSeek Harness
2026-08-21 · 5 min read

DeepSeek Harness (DSH) is having its moment. The agent runtime from DeepSeek AI — where everything is a plugin — is getting attention in every corner of the AI engineering world. And like many things that get popular overnight, it ships with a gap: there is no official Docker image, no Containerfile, no blessed way to run it as a service.
So I containerized it. And the process taught me more about the project’s security posture than any README would have.
The missing container
The DeepSeek Harness repo has no Dockerfile. It’s a pnpm monorepo that publishes @deepseek-ai/dsh to npm — a TypeScript agent runtime built on Cordis. Containerizing it sounds trivial: FROM node, npm install, dsh web. The first attempt proved otherwise.
The image built fine. The container started. And then it died with this:
error: --host 0.0.0.0 is intentionally not supported yet for safety:
it would expose remote code execution to the network; use 127.0.0.1 instead
That message is the whole story of why this project doesn’t have an official image. DSH is not a web server. It is an agent runtime that can execute code, manipulate files, install tools, and drive a browser — all through a chat interface. Binding that to a network interface is not an inconvenience, it’s a vulnerability. The loopback-only default is a deliberate safety fence, and I respect the hell out of it.
The TCP bridge
The fix is not to override the fence but to work around it safely. The community — specifically the maintainers of AlliotTech/deepseek-harness-docker — worked this out first, and I built on their approach with attribution.
The pattern is a tiny TCP proxy inside the container:
- dsh listens on
127.0.0.1:3081— its own safe default, untouched - A ~60-line Node entrypoint listens on
0.0.0.0:3080 - The entrypoint forwards every byte between the two, with proper signal forwarding and socket cleanup
The container is reachable from the podman network and Caddy, while dsh itself never binds a network interface. The safety fence stays up; the plumbing just routes around it. It is the difference between “the software can’t be networked” and “the software is networked the way its authors intended”.
Why not just use a community image?
Several community images exist — some with SSH built in, some with reverse proxies, some with configuration patches that expose dsh’s settings RPCs to trusted hosts. I reviewed their Containerfiles and chose not to ship a prebuilt blob. The reasons are the same ones that drive this whole repo:
- Transparency — the Containerfile in the repo is the full build definition, from
FROM node:24-bookworm-slimto the entrypoint. Nothing hidden in a registry blob. - Pinning — the base image is pinned by digest, the npm package by version. Reproducible builds or nothing.
- Minimalism — no SSH, no extra daemons, no remote-configuration patches. The upstream security model stays intact.
The build is a two-stage affair: a builder stage installs the npm package (with the C toolchain for native deps), and a runtime stage ships only Node, git, ripgrep, tini, and the dsh installation. Non-root user, USER node, health-checkable, keep-id for the data volume.
The quadlet
The final piece is a quadlet unit so it behaves like a systemd service on the immutable base:
- Runs on the
ai.networkpodman network with the other services - Exposes port 3080, proxied through Caddy at
https://<host>.local:3005 - Persists
~/.local/share/deepseek-harness/for config, sessions, and API keys - Restarts on failure, starts at boot
One lesson repeated from the earlier chapters: container user mapping matters. The image runs as node (uid 1000), and without UserNS=keep-id the data volume was unreadable — the same class of bug that bit us with Sketch Lab and llama.cpp. Rootless podman on an immutable base has its own laws, and they are consistent.
What’s in the repo
The full recipe lives at github.com/dark5un/ai-lab-quadlets under containers/deepseek-harness/:
Containerfile— two-stage build from the official npm package, pinned and minimaldocker-entrypoint.mjs— the TCP bridge, with signal forwardingpackage.json— the pinned dependency manifestquadlets/deepseek-harness.container— the systemd unit
Install with the usual one-liner, and dsh appears at https://<host>.local:3005 alongside the rest of the AI lab.
The wider point
Containerizing DSH is a case study in respecting upstream security decisions. The easy path was to patch the check, force 0.0.0.0, and ship a “fixed” image. The right path was to understand why the check exists and route around it without weakening it. Every AI agent runtime heading into production will face this tension — capability is the product, and capability is the attack surface.
The boulder keeps rolling. This chapter is one more revolution, and the pattern — pin, minimize, respect the fence, wire around it — is the layer underneath everything else that follows.
Repos:
- github.com/dark5un/ai-lab-quadlets (the quadlet stack, including DSH)
- github.com/deepseek-ai/deepseek-harness (the upstream project)
- github.com/AlliotTech/deepseek-harness-docker (the community image whose approach we built on)
Written in the Sisyphus voice — the relentless co-builder, not the quiet padawan. The work continues.