> ## Content Index
> Fetch the complete content index at: https://www.sredevops.org/llms.txt
> Use this file to discover other available public pages before exploring further.

# Cómo logré utilizar DeepSeek Harness (dsh) seguro y escalable en Docker y Kubernetes (Parte 1)
- URL: https://www.sredevops.org/es/como-logre-utilizar-deepseek-harness-dsh-seguro-y-escalable-en-docker-y-kubernetes-parte-1/
- Published: 2026-11-13T00:34:10.000Z
- Updated: 2026-11-13T00:34:10.000Z
- Description: Cuando lo quise correr en Kubernetes, la pregunta no era cómo levantarlo, sino qué necesita para no romperse, poder escalar y por sobre todo poder ejecutar el container de la forma más segura posible.
- Author: Nicolás Georger
- Tags: Kubernetes, Containers, Security, Artificial Intelligence, Español

🛠️

***Serie:** **Explorando una forma segura de correr DeepSeek Harness: cómo lo implementé en Kubernetes. Parte 1 de 2.*

Dejarías que un agente de IA, que lee tus archivos, lanza subprocesos, lee claves de API de proveedores, utiliza shells, puede alterar configuraciones de sistema y que además es de código propietario, se ejecute libremente en tus máquinas? *(cof, cof... claude)* Yo, al menos, no.

Es por eso que puse mi atención en [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness?ref=sredevops.org) (DSH): Es un harness para agentes autónomos, open source (licencia MIT) y que se basa en el concepto de "todo es un plugin", entre otras características que no vienen al caso en este momento y [claramente puedes ir a ver tu mismo](https://www.deepseek.com/en/harness/?ref=sredevops.org). **Detalle importante:** las versiones actuales se consideran como developer preview; por lo tanto los cambios incompatibles son parte anunciada del recorrido.

Cuando lo quise correr en Kubernetes, la pregunta no era cómo levantarlo, sino qué necesita para no romperse, poder escalar y por sobre todo poder ejecutar el container de la forma más segura posible. De ahí salió [sredevopsorg/deepseek-kubernetes-sandbox](https://github.com/sredevopsorg/deepseek-kubernetes-sandbox?ref=sredevops.org), el repo que mantenemos con la organización.

[GitHub - sredevopsorg/deepseek-kubernetes-sandbox: Run deepseek-harness (dsh) as a container in Kubernetes with agent-sandbox and gvisor isolation or locally with DockerRun deepseek-harness (dsh) as a container in Kubernetes with agent-sandbox and gvisor isolation or locally with Docker - sredevopsorg/deepseek-kubernetes-sandbox![](https://www.sredevops.org/content/images/icon/favicon-f50cc03a-5db7-4e8e-820a-537937b823e9.png)GitHub![](https://www.sredevops.org/content/images/thumbnail/deepseek-kubernetes-sandbox-67b433fd-6d17-45ec-83d6-a9f109ecbe76.png)](https://github.com/sredevopsorg/deepseek-kubernetes-sandbox?ref=sredevops.org)

Su artefacto principal es una imagen de DSH Web (`dsh web`), basada en debian trixie, slim, sin root y multiarquitectura (amd64 y arm64). Todo lo demás existe para sostener esa imagen o para mostrar una implementación sobre ella.

Esta primera parte es la imagen: cómo correrla, por qué su build se fía de digests y no de tags, y el contrato de runtime que la mantiene fuera de un crash loop. En la futura parte 2 explicaremos cómo desplegarla en Kubernetes, con aislamiento mucho más sólido y la posibilidad de utilizar oAuth2 para la gestión de distintos usuarios con su propio entorno preconfigurado.

## Inicio rápido: Ejecutar con Docker (o podman)

```bash
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.2-rc.2

docker logs dsh-web 2>&1 | grep -m1 '?token='
# dsh web: http://localhost:3080/?token=...

```

`-e DEEPSEEK_API_KEY` toma el valor desde el entorno de tu shell, así la clave nunca queda en la imagen ni en tu historial como argumento. El volumen con nombre guarda tanto el estado de DSH como el workspace del agente.

Abre `http://localhost:3080` y recibes un `401`. No es un bug. DSH Web no sirve para nada en un `/` pelado sin su cookie de token de lanzamiento, y el token de un solo uso aparece únicamente en los logs de arranque. Abre una vez la URL impresa con `?token=...`; DSH setea su cookie de autenticación y la UI funciona normal después. Si el puerto `3080` está tomado en tu equipo, mapea otro y define `DSH_PUBLIC_URL`, o la URL que aparece en el log va a apuntar a un puerto donde no escucha nadie.

Ese token trátalo como contraseña. Vive en tus logs, y los logs terminan en tres lugares que ya olvidaste.

![](https://www.sredevops.org/content/images/2026/11/dsh-docker.png)

## Un build que se niega a fiarse de los tags

El Dockerfile es un build de dos etapas, y las dos parten de imágenes `node` **fijadas por digest** en `versions.env`. La razón es simple: un tag se puede repuntar a otro build sin avisarte. El script de verificación del repo rechaza cualquier referencia a imagen base sin un `@sha256:`, y la etapa de build además clona DSH upstream en un ref fijo y comprueba que el `HEAD` del clon sea exactamente `DSH_COMMIT`. Si upstream mueve el tag, el build falla ruidosamente en vez de empaquetar en silencio otro código.

Después vienen las dependencias con pnpm (activado vía corepack en una versión fija), el build de producción y el CLI `@deepseek-ai/dsh` empaquetado con `pnpm pack`. En la etapa de runtime copio el resultado a `/app` sobre `node:24-trixie-slim`, también fijada por digest, y sumo las herramientas que los agentes de verdad usan: `git`, `ripgrep`, `jq`, `yq`, `curl`, `wget`, `gh`, `uv` y [tini](https://github.com/krallin/tini?ref=sredevops.org), más [bubblewrap](https://github.com/containers/bubblewrap?ref=sredevops.org) como runner preferido del sandbox de comandos de DSH.

[deepseek-kubernetes-sandbox/Dockerfile at main · sredevopsorg/deepseek-kubernetes-sandboxRun deepseek-harness (dsh) as a container in Kubernetes with agent-sandbox and gvisor isolation or locally with Docker - sredevopsorg/deepseek-kubernetes-sandbox![](https://www.sredevops.org/content/images/icon/favicon-3c1e7eea-d5c0-4d38-ad8b-b92fe3d326c3.png)GitHub![](https://www.sredevops.org/content/images/thumbnail/b354f769-47bb-4b05-acc5-630ee0d7eb81-5c5e771d-58b6-43c6-83a6-50afdde05ff5.png)](https://github.com/sredevopsorg/deepseek-kubernetes-sandbox/blob/main/Dockerfile?ref=sredevops.org)

`make image` es el punto de entrada canónico; cada pin vive en `versions.env` y `make check` falla cuando uno se desvía. Las labels de la imagen registran el ref de DSH upstream y la revisión de release, así puedes ir de un contenedor corriendo a un commit sin adivinar.

Los tags son promesas mutables; los digests y los commits SHA son contratos. Este repo confía en los contratos auditables.

## El contrato de runtime

### Un volumen, dos hogares

El estado de DSH — conversaciones, sesiones, configuración, credenciales — vive en la raíz de `/home/node/.dsh` (`DSH_HOME`). El workspace del agente es su subdirectorio `workspace/`, que también es el `HOME` y el `WORKDIR` de la imagen.

Ese layout lo puse a propósito. El selector de carpetas de DSH abre en `$HOME` y no tiene ajuste de ruta inicial, así que apuntar `HOME` dentro del volumen hace que persistan el selector, tus ejecuciones, las cachés de `npm`/`uv`/`git` y los skills globales de DSH en `~/.agents`. Monta un volumen en `DSH_HOME` y todo sobrevive a los reinicios.

0:00 

/1:00 

1× 

ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox:dsh-v0.2.2-rc.2 ejecutandose en Podman local, validando entorno de ejecución y programas disponibles

> En Kubernetes, las plantillas que trae el repo agregan un segundo montaje del subPath `workspace` del *mismo* PVC en `/home/node/.dsh/workspace`. No es un segundo volumen: es un truco para que el kubelet cree el directorio y le cambie el dueño antes de que arranque el contenedor. Sin eso, el runtime crea el directorio de trabajo faltante como root, y el usuario `node` sin privilegios (UID/GID 1000) no puede escribir en su propio home.

> Docker tiene la trampa espejo: un volumen con nombre creado por una imagen vieja no se repuebla desde la nueva. Crea `workspace/` una vez con dueño `1000:1000` antes de arrancar. Un volumen nuevo copia el directorio de la imagen automáticamente, que es lo que la [semántica de volúmenes de Docker](https://docs.docker.com/engine/storage/volumes/?ref=sredevops.org) busca.

### Probes, puertos y señales

`/` no es un endpoint de salud. Usa `/favicon.svg`: no requiere autenticación, lo sirve la capa web y prueba que el servidor HTTP está arriba sin disparar el flujo de auth. Un liveness probe contra `/` va a mandar tu pod a crash loop con una constancia admirable.

Por lo mismo le puse al `startupProbe` dos minutos de presupuesto: la activación de plugins puede pasarse de un liveness por defecto.

```yaml
startupProbe:
  httpGet: {path: /favicon.svg, port: 3080}
  periodSeconds: 5
  failureThreshold: 24
readinessProbe:
  httpGet: {path: /favicon.svg, port: 3080}
  periodSeconds: 5
livenessProbe:
  httpGet: {path: /favicon.svg, port: 3080}
  periodSeconds: 15

```

El resto del contrato: un solo puerto (`3080`) sirve la UI, los assets, la API, SSE y WebSocket. `tini` corre como PID 1, cosecha zombies y reenvía `SIGTERM`; el entrypoint hace `exec` de `dsh web` para que las señales lleguen de verdad a Node. El proceso no es root, `DSH_HOST` detecta sola la IP del pod cuando queda vacío y `NODE_ENV=production` está definido. Detrás de un proxy, agrega tu hostname público a `DSH_TRUSTED_HOSTS`: DSH rechaza los `Host` que no conoce, y solo `localhost` y `127.0.0.1` son siempre de confianza.

[deepseek-kubernetes-sandbox/Dockerfile at 21e94de0657c279472fcff7724b58a228adbd2b9 · sredevopsorg/deepseek-kubernetes-sandboxRun deepseek-harness (dsh) as a container in Kubernetes with agent-sandbox and gvisor isolation or locally with Docker - sredevopsorg/deepseek-kubernetes-sandbox![](https://www.sredevops.org/content/images/icon/favicon-6008e520-e7b0-47af-aa29-c29092bb2e90.png)GitHub![](https://www.sredevops.org/content/images/thumbnail/deepseek-kubernetes-sandbox-779372ef-3a21-4d5d-bd29-2cd7c15672f2.png)](https://github.com/sredevopsorg/deepseek-kubernetes-sandbox/blob/21e94de0657c279472fcff7724b58a228adbd2b9/Dockerfile?ref=sredevops.org)

## El sandbox dentro del sandbox

Aquí está la parte que casi todas las demos se saltan. DSH mete sus propios comandos bash en un sandbox, una segunda capa de aislamiento dentro del sandbox de contenedor que ya corres. Sus runners en Linux son bubblewrap y luego [Landlock](https://docs.kernel.org/userspace-api/landlock.html?ref=sredevops.org), y **falla cerrado** con `SANDBOX_UNAVAILABLE` en vez de correr un comando fuera del sandbox.

![](https://www.sredevops.org/content/images/2026/11/image.png)

**Opciones para DSH\_PERMISSION\_MODE*

`DSH_PERMISSION_MODE` define la política: `read-only`, `workspace-write` (el default de la imagen) o `danger-full-access`, que permite ejecutar los procesos con los mismos permisos del usuario de la máquina, además desactiva las solicitudes de aprobación.

Elige el último **sólo** cuando el pod o la VM que lo ejecuta, sea la *"frontera de aislamiento"* en la que de verdad confías. En éste caso, si estás ejecutando la imagen en tu máquina local, sin Docker rootless, no es la mejor idea. En mi caso, utilizo Podman, que al menos en versiones recientes de Linux, se instala por defecto en modo rootless, por lo tanto me da un pequeño boost de aislamiento (insisto, PEQUEÑO), para testear los distintos 

Y ojo con esto: el workspace vive *dentro* de `DSH_HOME`, así que un shell que se pasea por el directorio padre puede llegar a `.credentials.yaml`. El contenedor nunca fue un secreto para el agente.

**En la parte 2 muestro la respuesta con gVisor y NetworkPolicy a ese párrafo incómodo.**

## Credenciales: inyectadas, nunca baked

DSH resuelve las API Keys desde los nombres estándar del entorno donde se ejecuta, en el caso que quieras usar algún otro provider, ejemplos: `DEEPSEEK_API_KEY`, `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` (no `GOOGLE_API_KEY`), `MISTRAL_API_KEY`, `HF_TOKEN` y el resto de su catálogo de proveedores. Las inyecto con `-e` en Docker/Podman o con un Secret con `envFrom` en Kubernetes. **Nunca dentro de una imagen ni en un commit.**

![](https://www.sredevops.org/content/images/2026/11/image-3.png)

Un detalle de orden importa durante la rotación: una clave guardada en el almacén de credenciales de DSH (`$DSH_HOME/.credentials.yaml`) **tiene prioridad sobre el entorno**. Rota solo el Secret y puede que no cambie nada. Si falta una clave tampoco se rompe la UI; solo fallan las llamadas a modelos.

## Releases inmutables, digests fijados

La identidad de release es el ref de DSH upstream más una revisión del repositorio: `DSH_VERSION=dsh-v0.2.2-rc.2` con `RELEASE_REVISION=r3` da `dsh-v0.2.2-rc.2-r3`. Los tags son inmutables; un release roto lo arreglo publicando `-r(N+1)`, nunca re-etiquetando.

Publico la imagen multiarquitectura (`linux/amd64`, `linux/arm64`) en GitHub Container Registry:

| Tag             | Significado                                                  |
| --------------- | ------------------------------------------------------------ |
| dsh-v0.2.2-rc.2 | Identidad de release actual; no se mueve una vez publicada   |
| latest          | Release más reciente; mismo digest que el tag de identidad   |
| main            | Canal de desarrollo; no es un release, no es para producción |

Los despliegues en producción deberían fijar el digest, y los builds de release publican attestations de SBOM y de procedencia: metadatos sobre qué estás corriendo y cómo se construyó, no un nombre que esperas que siga significando lo mismo que ayer:

```bash
docker buildx imagetools inspect ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox:dsh-v0.2.2-rc.2
# pin in your manifests:
#   image: ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox@sha256:<digest>

```

## Qué veremos en la Parte 2?

Un contenedor es una demo; la plataforma es el objetivo. En la parte 2 construyo sobre esta imagen con [kubernetes-sigs/agent-sandbox](https://github.com/kubernetes-sigs/agent-sandbox?ref=sredevops.org): una instancia de DSH por usuario aprovisionada desde un warm pool, suspendida cuando está ociosa con el estado intacto en PVCs, y al frente un gateway de navegador que hace el handshake del token de lanzamiento del lado del servidor y expone GitHub SSO en el borde. Encima de eso, el hardening opt-in con gVisor y NetworkPolicy al que este post no deja de apuntar.

Parte por el [ejemplo de agents-as-a-service](https://github.com/sredevopsorg/deepseek-kubernetes-sandbox/tree/main/examples/dsh-agents-as-a-service?ref=sredevops.org). Mientras tanto: fija tus digests, hazle probe al favicon y no dejes que el primer acto de tu agente sea leer sus propias credenciales.

## Referencias

- [README de sredevopsorg/deepseek-kubernetes-sandbox](https://github.com/sredevopsorg/deepseek-kubernetes-sandbox/blob/main/README.md?ref=sredevops.org)
- [La imagen principal de DSH Web: build, contrato de runtime y probes](https://github.com/sredevopsorg/deepseek-kubernetes-sandbox/blob/main/docs/main-image.md?ref=sredevops.org)
- [Repositorio de DeepSeek Harness y aviso de developer preview](https://github.com/deepseek-ai/deepseek-harness?ref=sredevops.org)
- [Subsistema de sandbox de procesos de DSH: modos, runners y comportamiento fail-closed](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/sandbox.md?ref=sredevops.org)
- [Volúmenes de Docker](https://docs.docker.com/engine/storage/volumes/?ref=sredevops.org)
- [tini](https://github.com/krallin/tini?ref=sredevops.org), [bubblewrap](https://github.com/containers/bubblewrap?ref=sredevops.org), [documentación de Landlock en el kernel](https://docs.kernel.org/userspace-api/landlock.html?ref=sredevops.org)