1
0
mirror of https://github.com/sredevopsorg/deepseek-kubernetes-sandbox.git synced 2026-11-03 01:13:24 +00:00
SREDevOps.org Agent a09d4ac340 fix: ship bubblewrap for DSH's command sandbox, keep agents usable without it (#35)
* fix(example): keep agents usable when DSH's inner sandbox has no backend

DSH confines bash commands itself (bubblewrap, else Landlock) and fails closed
with SANDBOX_UNAVAILABLE when no runner is usable. In this hardened pod -
non-root, capabilities dropped, seccomp RuntimeDefault - bubblewrap cannot
start (it needs CAP_SYS_ADMIN) and the pod exposes no Landlock runner, so the
shipped workspace-write default made every command fail:

  sandbox mode "workspace-write" is requested but no sandbox backend is usable

The DSH base bundle at the pinned ref documents DSH_PERMISSION_MODE as the
explicit deployment override, so set it to danger-full-access in both sandbox
templates. The per-user pod is the isolation boundary in this example (its own
pod, PVC and Service; non-root; all capabilities dropped), so a working agent
is worth more than an inner file policy that cannot be enforced. Note that
danger-full-access also turns approval prompts off.

Trade-off documented rather than hidden: an agent command can now read its own
DSH_HOME (including .credentials.yaml) and write anywhere in the container.
Hosts where a runner works should keep workspace-write or read-only.

- main-image docs: DSH_PERMISSION_MODE row plus a note on the runners and the
  fail-closed behavior
- example README: limits bullet and a troubleshooting row with the in-pod
  backend check and how to restore a confined mode
- architecture: tenant-isolation bullet extended
- skill runbook/troubleshooting: operation guidance; never grant CAP_SYS_ADMIN
  or seccompProfile: Unconfined to "fix" this without an explicit decision

Verified: make check (14 manifests), make lint, YAML assertions on both
templates, and the override expression at the pinned dsh-v0.2.1-rc.5 base
bundle (mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write').

* fix(image): install bubblewrap for DSH's command sandbox

DSH's Linux sandbox runner chain is bubblewrap, then Landlock, and it fails
closed with SANDBOX_UNAVAILABLE when neither works. The image shipped neither,
so a sandboxed agent had no way to execute confined commands.

Add bubblewrap to the runtime stage. Debian ships it unsetuid (0.12.0), so it
still needs unprivileged user namespaces plus the namespace/mount syscalls: in a
hardened pod (all capabilities dropped, RuntimeDefault seccomp) it usually
cannot create one, and DSH then falls back to Landlock or, failing that, to the
DSH_PERMISSION_MODE=danger-full-access override the templates already set.
Where the host allows an unprivileged user namespace, the first-choice runner
now exists, so the confined modes can be restored.

Docs corrected to match: bubblewrap is no longer "absent"; the blocker is the
unsetuid binary's namespace requirement, not a missing CAP_SYS_ADMIN grant.

Verified: make check, make lint; the package exists in the pinned base
(node:24-trixie-slim, bubblewrap 0.12.0-1~deb13u1); a throwaway container with
the pod's flags (--user 1000 --cap-drop ALL --security-opt no-new-privileges)
still cannot create a namespace, which is why the override stays. No local image
build was run.

* ci(image): build and smoke-test the main image on PRs that touch it

The Dockerfile and the pins in versions.env could only be validated by a merge
to main or by running `make image` by hand; neither gives a PR-time signal.

Add ci-image.yaml: on pull requests that change Dockerfile, .dockerignore or
versions.env (and on manual dispatch) it builds linux/amd64 natively with the
pins sourced from versions.env - no registry login, no push - loads the image
locally and smoke-tests it:

- the packaged CLI runs and reports the pinned version
- the container is the non-root node user (1000) and owns /workspace + DSH_HOME
- the bubblewrap command-sandbox runner is present
- the OCI version label matches versions.env
- a container serves /favicon.svg (the same unauthenticated probe the shipped
  manifests use), with logs filtered so the launch token is never printed

Path-filtered like the other on-demand checks, so it is not a required status
check; the published multi-arch build stays in build-main.yaml and release.yaml.

Docs updated in the same change: AGENTS.md repository map and testing table,
docs/gitflow.md checks section, docs/main-image.md build section.

Verified: actionlint 1.7.12 and yamllint 1.38.0 (CI versions) clean over the
whole repository, plus make check and make lint.

---------

Co-authored-by: SREDevOpsAgents <agents@sredevops.org>
2026-10-30 08:36:21 -03:00
2026-10-25 08:37:43 -03:00

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.5

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.5
# then open http://localhost:18081/?token=...

Hosts, probes and the 401

  • Probes must not hit /. It returns 401 without the launch token, so a readiness/liveness probe on / crash-loops the pod. Probe /favicon.svg instead: it is unauthenticated. The shipped manifests use it.
  • Host allow-listing. DSH rejects unknown Host headers. localhost and 127.0.0.1 are always trusted; add any other public hostname to DSH_TRUSTED_HOSTS (comma or space separated). A gateway or Ingress that forwards the browser's Host needs 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).

DSH also resolves the standard per-provider environment names — OPENROUTER_API_KEY, GEMINI_API_KEY, NVIDIA_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, GROQ_API_KEY, XAI_API_KEY, HF_TOKEN, and others — through its packaged provider catalog. Inject them the same way; a key saved in DSH's credential store wins over the environment. See provider credentials.

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.5 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.

Tags published before the release-identity scheme remain on GHCR but are not updated; new deployments use the current identity.

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.5.

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.5
# 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, and opt-in gVisor/NetworkPolicy/provider-key hardening. 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). The example also documents opt-in gVisor, nodeSelector, NetworkPolicy and extra provider-key hardening in optional hardening.

Agent skills

DSH discovers project skills from .dsh/skills/<name>/SKILL.md. The repository ships dsh-agent-sandbox-platform, which covers operating and extending the platform, the optional agent-sandbox MCP server, and a read-only cluster preflight. Runbooks, the MCP reference, the extension guide and troubleshooting notes live beside SKILL.md.

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.
.dsh/skills/ Project-local DSH skills, auto-discovered from the git root.
.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.5 — the first release at this upstream DSH ref, so it carries no repository revision. 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.

Description
Run deepseek-harness (dsh) with agent-sandbox in Kubernetes
Readme MIT 743 KiB
Languages
Shell 64.3%
Dockerfile 19.2%
Makefile 11.6%
Python 4.9%