Update AGENT_SANDBOX_VERSION to v1.0.8 and the docs that quote the control-plane pin (example prerequisites, architecture, versioning table, minimal-single-tenant template comment, platform skill). The v1.0.8 release adds only one CRD field, spec.failedPodPolicy (Sandbox and SandboxTemplate, enum Retain|Recreate, default Retain), so the existing manifests keep their behavior: Retain is the default and neither the API version (v1beta1) nor any field we set changed. All five custom resources still validate against the v1.0.8 CRD schemas. Behavior changes in the controller itself (bounded MCP output, router kustomization, creation-latency metric timing) do not affect the platform because it uses neither the sandbox-router nor the MCP server. Co-authored-by: SREDevOpsAgents <agents@sredevops.org>
Minimal single-tenant DSH Web
The smallest Kubernetes deployment of DeepSeek Harness (DSH) Web: one
SandboxTemplate, a one-replica warm pool and one claim. It is the "just one
DSH Web instance" quickstart.
For the full platform — warm pool sizing, per-user claims, a browser-facing
gateway with cookie sessions, idle-suspend and wake-on-connect — see
examples/dsh-agents-as-a-service/.
This example deliberately has no gateway: you reach DSH through a
kubectl port-forward and its launch-token URL.
Prerequisites
-
A Kubernetes cluster with a default StorageClass (kind and GKE both work).
-
The agent-sandbox controller with extensions (API
v1beta1) installed. The version is pinned asAGENT_SANDBOX_VERSIONinversions.env:# from the repository root: . versions.env kubectl apply -f "https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${AGENT_SANDBOX_VERSION}/sandbox-with-extensions.yaml" kubectl -n agent-sandbox-system rollout status deployment --timeout=180s -
A DeepSeek API key for real model calls. The pod starts without one; only model requests fail. Optionally create
dsh-provider-keys-extrafor OpenRouter, Gemini, NVIDIA or other provider keys (the template references it withoptional: true).
Quickstart
Run these from this example's directory (examples/minimal-single-tenant/).
# 1. Namespace + shared provider keys. They are injected into the sandbox via
# envFrom and are never committed.
kubectl create namespace dsh-demo
kubectl -n dsh-demo create secret generic dsh-provider-keys \
--from-literal=DEEPSEEK_API_KEY="sk-..."
# Optional: extra providers, injected through a second Secret. Create it before
# the warm pool so the pods are stamped with the keys.
kubectl -n dsh-demo create secret generic dsh-provider-keys-extra \
--from-literal=OPENROUTER_API_KEY="sk-or-..." \
--from-literal=GEMINI_API_KEY="..." \
--from-literal=NVIDIA_API_KEY="nvapi-..."
# 2. The template itself (image pinned in the manifest).
kubectl apply -f manifests/sandbox-template.yaml
# 3. A template only describes pods; a warm pool turns it into running
# Sandboxes and a claim adopts one. One replica is enough here.
kubectl apply -f - <<'EOF'
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxWarmPool
metadata:
name: dsh-pool
namespace: dsh-demo
spec:
replicas: 1
sandboxTemplateRef:
name: dsh-web-template
---
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxClaim
metadata:
name: dsh
namespace: dsh-demo
spec:
warmPoolRef:
name: dsh-pool
EOF
kubectl -n dsh-demo wait sandboxclaim/dsh --for=condition=Ready --timeout=180s
Talk to the instance
DSH gates / behind a launch token it prints only in its startup logs. Open
the printed URL once; DSH then sets its own auth cookie and the UI loads
normally.
SB=$(kubectl -n dsh-demo get sandboxclaim dsh -o jsonpath='{.status.sandbox.name}')
kubectl -n dsh-demo logs "$SB" | grep -m1 '?token='
# dsh web: http://<pod-ip>:3080/?token=...
kubectl -n dsh-demo port-forward "pod/$SB" 18081:3080
# open http://localhost:18081/?token=... from the log line
Port note: DSH Web listens on
3080in-cluster. If3080is already in use on your workstation, forward to another local port (here18081), never3080.localhostis always in DSH's trusted-host list, so the forwarded request is accepted.gVisor caveat: if you uncomment
runtimeClassName: gvisorin the template,kubectl port-forwardno longer works. Use the multi-tenant gateway example or a reachable Service instead; see optional hardening.
Network policy and optional hardening
The template omits networkPolicyManagement, so the agent-sandbox controller
uses its Managed secure default: ingress only from the sandbox-router, public
egress, and no internal cluster DNS. kubectl port-forward is local to the pod
and still works (except under gVisor).
gVisor, an optional nodeSelector, extra provider keys and a stricter sandbox
NetworkPolicy are documented in
optional hardening.
Probes and trusted hosts
Two settings in manifests/sandbox-template.yaml are easy to get wrong:
- Probe path.
/returns401without the launch-token cookie, so a probe there crash-loops the pod. The template probes/favicon.svg(served unauthenticated) and gates liveness/readiness behind astartupProbe, which gives a cold start (plugin activation, or the first resume) up to 120s before a restart. - Host allow-list. DSH rejects unknown
Hostheaders.localhostand127.0.0.1are always trusted; if you later front the sandbox with an Ingress or proxy, setDSH_TRUSTED_HOSTSto that public host (comma or space separated). The full example explains both exposure variants inmanifests/exposure/README.md.
The template also sets fsGroupChangePolicy: OnRootMismatch. DSH's
credentials plugin requires .credentials.yaml to be 0600, while the
kubelet's default fsGroup policy rewrites it to 0660 on every pod start —
without this policy a recreated or resumed sandbox crash-loops. The rationale
is spelled out in
10-sandboxtemplate.yaml.
Suspend, resume and clean up
# Suspend: the pod is deleted, the PVC (state) and Service are kept.
kubectl -n dsh-demo patch sandbox "$SB" --type merge -p '{"spec":{"operatingMode":"Suspended"}}'
# Resume: a new pod reattaches the same PVC; conversations survive.
kubectl -n dsh-demo patch sandbox "$SB" --type merge -p '{"spec":{"operatingMode":"Running"}}'
kubectl -n dsh-demo wait sandbox "$SB" --for=condition=Ready --timeout=180s
# Delete everything this quickstart created.
kubectl -n dsh-demo delete sandboxclaim dsh
kubectl delete namespace dsh-demo
Deleting the claim cascades to the sandbox and its PVC.