* feat(repo)!: restructure around the dsh-harness image and one example per implementation
- the root Dockerfile is the primary artifact; versions.env is the single source of truth
- merge gateway-api/dsh-demo into examples/dsh-agents-as-a-service (manifests/, exposure variants)
- move the single-tenant template into examples/minimal-single-tenant
- publish the gateway as a nested package under the repository image
- mirror the DSH ref for release identities (first release dsh-v0.2.1-rc.2-r2)
- CI: lint/python/manifests/gateway-image, reusable multi-arch build, tag-driven release, weekly e2e
- docs: README, docs/{main-image,examples,gitflow,versioning}, example READMEs, AGENTS.md, governance
- SSO is opt-in again; gateway pull policy and cookie defaults match the documented demo
BREAKING CHANGE: example manifest paths, the gateway image name, and the removal
of deploy/ and gateway-api/ affect existing deployments; see the migration table
in README.md.
* docs(changelog): generate initial changelog from history
* fix(scripts): make set-version update Dockerfile and sample revision pins
* build(hadolint): document intentional lint exceptions for the main image
---------
Co-authored-by: SREDevOpsAgents <agents@sredevops.org>
deepseek-kubernetes-sandbox
DeepSeek Harness Web, packaged as a container. This repository's primary
artifact is a slim, non-root, multi-arch image of
DeepSeek Harness Web
(dsh web), ready for Docker and Kubernetes. Everything else exists to support
that image or to demonstrate an implementation on top of it.
The flagship implementation is
examples/dsh-agents-as-a-service/: the
"agents-as-a-service" platform pattern with one DSH Web instance per user,
provisioned from a warm pool on
kubernetes-sigs/agent-sandbox,
suspended when idle, and fronted by a small browser-usable gateway.
Quick start
docker run -d --name dsh-web \
-p 3080:3080 \
-v dsh-home:/home/node/.dsh \
-e DEEPSEEK_API_KEY \
ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox:dsh-v0.2.1-rc.2-r2
DSH Web is not usable at a bare /. Without its launch-token cookie it
answers 401, and the token is printed only in the startup logs:
docker logs dsh-web 2>&1 | grep -m1 '?token='
# dsh web: http://localhost:3080/?token=...
Open the printed ?token=... URL once: DSH sets its auth cookie and the UI loads
normally afterwards. Hitting / directly (or after the cookie expires) returns
401 by design.
Host port and public URL
If 3080 is taken locally, map another port and set the public URL so the quoted
token URL matches:
docker run -d --name dsh-web -p 18081:3080 \
-e DSH_PUBLIC_URL=http://localhost:18081 \
-v dsh-home:/home/node/.dsh \
-e DEEPSEEK_API_KEY \
ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox:dsh-v0.2.1-rc.2-r2
# then open http://localhost:18081/?token=...
Hosts, probes and the 401
- Probes must not hit
/. It returns401without the launch token, so a readiness/liveness probe on/crash-loops the pod. Probe/favicon.svginstead: it is unauthenticated. The shipped manifests use it. - Host allow-listing. DSH rejects unknown
Hostheaders.localhostand127.0.0.1are always trusted; add any other public hostname toDSH_TRUSTED_HOSTS(comma or space separated). A gateway or Ingress that forwards the browser'sHostneeds its hostname listed there. - State lives in
/home/node/.dsh. Mount a volume (Docker) or PVC (Kubernetes) to keep conversations, sessions and config across restarts.
Runtime environment
| Variable | Default | Purpose |
|---|---|---|
DEEPSEEK_API_KEY |
empty | Provider credential consumed by DSH. Inject it; never bake it into the image or commit it. |
DSH_PORT |
3080 |
Port DSH Web listens on inside the container. |
DSH_HOST |
auto-detected pod/container IP | Bind address. Leave empty in Kubernetes. |
DSH_PUBLIC_URL |
http://localhost:${DSH_PORT} |
Public base URL used in generated links and the launch-token URL in the logs. |
DSH_TRUSTED_HOSTS |
empty | Extra trusted Host/Origin entries, comma/space separated. |
DSH_HOME |
/home/node/.dsh |
DSH state directory (mount a volume here). |
Full contract, build args, probes, volumes and signals: docs/main-image.md.
Published images
Published to GitHub Container Registry. The main image is the repository image; example components are nested packages below it:
ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox # DSH Web (main image)
ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox/dsh-agents-as-a-service/gateway # example gateway
| Tag | Contents | Meaning |
|---|---|---|
dsh-v0.2.1-rc.2-r2 |
DSH Web, port 3080 |
Current release identity. Immutable: once published it never moves. |
latest |
DSH Web | Most recent release; same digest as the current identity. |
main |
DSH Web | Development channel rebuilt from every push to main. Not a release; not for production. |
dsh-v0.2.1-rc.2 |
DSH Web | Legacy tag published before the release-identity scheme. It is not updated. New deployments use dsh-v0.2.1-rc.2-r2. |
The example gateway is published under its nested path with the same release
identity, e.g.
ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox/dsh-agents-as-a-service/gateway:dsh-v0.2.1-rc.2-r2.
Release identities, channels and rollback rules are explained in docs/versioning.md. Production deployments should pin a digest:
docker buildx imagetools inspect ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox:dsh-v0.2.1-rc.2-r2
# image: ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox@sha256:<digest>
Build from source
make image # build the main DSH Web image (clones and builds upstream DSH)
make run # build it, then run it interactively (needs DEEPSEEK_API_KEY)
The pins (DSH_VERSION, RELEASE_REVISION, PNPM_VERSION) come from
versions.env — never hard-code them
(make check verifies). build.sh is a deprecated shim that
forwards to make; use the Makefile targets.
Examples
Self-contained implementations that consume the image. Index and contract:
examples/README.md and
docs/examples.md.
| Example | What it demonstrates |
|---|---|
examples/dsh-agents-as-a-service/ |
Full multi-tenant platform: SandboxTemplate with per-user PVCs, warm pool, one claim per user, suspend-when-idle, wake-on-return, browser gateway with signup + HttpOnly sessions + HTTP/WebSocket proxy, Ingress or Gateway API + GitHub SSO exposure. Start with the walkthrough and the architecture. |
examples/minimal-single-tenant/ |
The smallest useful setup: one SandboxTemplate and one SandboxClaim, reached through kubectl port-forward + the launch-token URL. Manifest: manifests/sandbox-template.yaml. |
The gateway is a component of the multi-tenant example and has its own README (API, configuration, auth/SSO, limitations).
Repository layout
| Path | Purpose |
|---|---|
Dockerfile |
Primary artifact: the multi-stage DSH Web image (port 3080). |
versions.env |
Single source of truth for the DSH ref, repository revision, pnpm/control-plane versions and image names. |
Makefile, build.sh |
Canonical local entrypoint; build.sh is a deprecated shim. |
docs/ |
Repository docs: image contract, example contract, gitflow, versioning. |
examples/ |
One self-contained implementation per directory. |
examples/dsh-agents-as-a-service/ |
Multi-tenant platform example: manifests/, gateway/, docs/, scripts/. |
examples/minimal-single-tenant/ |
Single-sandbox quickstart. |
scripts/ |
Repository checks: versions, example layout, manifest YAML, version bump. |
.github/ |
CI, release automation, dependency updates, PR/issue templates. |
.env.sample |
Documented local configuration (copy to .env; never commit it). |
Development
The Makefile is the canonical entrypoint. No cluster is needed for check,
test, lint, gateway or image:
| Target | What it does |
|---|---|
make check |
Version pins, example layout and manifest YAML checks. Run before every commit. |
make test |
Gateway unit tests (pytest). |
make lint |
ruff + shellcheck (+ yamllint/actionlint when installed). |
make image |
Build the main DSH Web image. |
make gateway |
Build the example gateway image locally (dsh-aaas-gateway:dev). |
make e2e |
Full kind walkthrough of the multi-tenant example (needs a cluster with agent-sandbox). |
make changelog |
Regenerate CHANGELOG.md with git-cliff (release only). |
make set-version |
Bump pins in versions.env; e.g. make set-version DSH_VERSION=<ref> RELEASE_REVISION=rN. |
Local setup and the PR workflow: CONTRIBUTING.md; branches, required checks and releases: docs/gitflow.md.
Versioning and releases
The release identity is the upstream DSH ref plus an optional repository
revision: DSH_VERSION and RELEASE_REVISION in versions.env.
The current identity is dsh-v0.2.1-rc.2-r2 — the first release under the
mirrored-DSH scheme at the same upstream DSH ref as the legacy
dsh-v0.2.1-rc.2 tag. Tags are immutable; a broken release is fixed by
publishing -r(N+1), never by re-tagging.
CHANGELOG.md is generated by git-cliff from
Conventional Commits and is never edited by hand. See
CHANGELOG.md, docs/versioning.md and
cliff.toml.
Migrating from the previous layout
If you are coming from an older checkout, these paths moved. Nothing else changed semantically; the content was split into the example it belongs to.
| Old path | New path |
|---|---|
gateway-api/dsh-demo/ (Gateway API + oauth2-proxy / GitHub SSO) |
examples/dsh-agents-as-a-service/manifests/exposure/gateway-api-sso/ (now 70-gateway.yaml ... 74-tls-secret.yaml.example) |
examples/dsh-agents-as-a-service/60-ingress.yaml |
examples/dsh-agents-as-a-service/manifests/exposure/ingress/60-ingress.yaml |
examples/dsh-agents-as-a-service/NN-*.yaml (00- ... 50-) |
examples/dsh-agents-as-a-service/manifests/NN-*.yaml |
examples/dsh-agents-as-a-service/run-test-kind.sh |
examples/dsh-agents-as-a-service/scripts/e2e-kind.sh |
deploy/k8s/sandbox-template.yaml |
examples/minimal-single-tenant/manifests/sandbox-template.yaml |
docs/architecture.md |
examples/dsh-agents-as-a-service/docs/architecture.md |
The SSO walkthrough that lived in gateway-api/dsh-demo/README.md is merged
into the multi-tenant example README
(step 9) and the exposure README.
Contributing
- CONTRIBUTING.md — local setup, PR workflow, what a change needs.
- AGENTS.md — repository conventions for AI coding agents (and humans) and the CI commands.
- SECURITY.md — private vulnerability reporting and project threat notes.
- CODE_OF_CONDUCT.md — community standards.
- docs/gitflow.md — branches, protected
main, required checks, release process.
Do not commit secrets; secret-shaped YAML lives in *.yaml.example files, and
version pins change only through versions.env / make set-version.
License
MIT © Nicolás Georger.