Files
apps-registry/workloads/docs-portal/docs/arquitectura/sesion-tmux-persistente.md
T
devops ba2b290523 docs: documenta la sesion tmux persistente para Claude Code
Explica el problema que resuelve (persistencia de sesion ante cortes
de red/dispositivo, distinto de limites de cuota), la arquitectura
del contenedor tmux-host, y por que Docker en vez de instalacion
nativa (squashfs read-only en ZimaOS).
2026-08-27 22:12:48 -05:00

5.7 KiB

Sesión persistente de Claude Code (tmux en Docker)

Problema que resuelve

Correr Claude Code directo en una shell SSH sobre el NAS lo ata al ciclo de vida de esa conexión: un corte de red, cambiar de laptop a tablet, o cerrar la terminal por accidente mata el proceso y con él todo el contexto de trabajo en curso — no hay forma de "retomar donde quedó" porque el proceso ya no existe.

Esto es un problema distinto al de los límites de uso/cuota de Claude Code. Este setup no aumenta cuota de mensajes ni tokens — eso lo controla el backend de Anthropic sin importar dónde corra el CLI. Lo único que resuelve es que la sesión de terminal sobreviva a cortes de red o cambios de dispositivo; si la cuota se agota, sigue agotada exactamente igual dentro de este setup.

Por qué Docker y no instalación nativa

ZimaOS no tiene gestor de paquetes tradicional utilizable para esto: / está montado como squashfs de solo lectura (imagen del SO inmutable), y /usr es un overlay de solo lectura sobre eso — /usr/local/bin ni siquiera existe y no se puede crear, tampoco como root (mkdir falla con "Read-only file system"). No hay dónde instalar Node.js, npm ni tmux directamente sobre el host.

Un contenedor Docker sortea esto por completo: tiene su propio filesystem de escritura (overlay2), independiente del squashfs del host — ahí es donde npm install -g deja el binario de claude sin tocar el host en absoluto. Lo único que el contenedor toma del host es lo que se monta explícitamente como volumen.

Arquitectura

flowchart LR
    SSH["Cliente SSH<br/>(laptop / tablet / lo que sea)"] -->|docker exec| C["Contenedor tmux-host<br/>--network host, --restart unless-stopped"]
    C --> T["tmux server<br/>sesión 'lab'"]
    T --> CC["proceso claude<br/>(Claude Code)"]
    C -.->|bind mount| DATA["/DATA (host)<br/>repos y configs del lab"]
    C -.->|bind mount| SOCK["/var/run/docker.sock (host)<br/>control de Docker/k3d"]
    C -.->|bind mount| HOME["/DATA/AppData/k3d-lab-v3/claude-code-home (host)<br/>-> /root/.claude"]

El contenedor sigue vivo aunque la conexión SSH se corte — es el servidor tmux, no la shell del cliente, el que sostiene el proceso de Claude Code. --restart unless-stopped además lo revive si el NAS reinicia.

Contenedor: tmux-host

docker run -d \
  --name tmux-host \
  --restart unless-stopped \
  -v /DATA:/DATA \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /DATA/AppData/k3d-lab-v3/claude-code-home:/root/.claude \
  --network host \
  alpine sh -c "apk add --no-cache tmux docker-cli bash curl git kubectl nodejs npm && npm install -g @anthropic-ai/claude-code && tail -f /dev/null"
Volumen Destino Por qué
/DATA /DATA Acceso a los repos (apps-registry, platform-infra, scripts) y configs del lab, mismos paths que en el host
/var/run/docker.sock /var/run/docker.sock Seguir controlando Docker y k3d (contenedores, k3d node, etc.) desde dentro del contenedor
/DATA/AppData/k3d-lab-v3/claude-code-home /root/.claude Persistir login/config de Claude Code — sobrevive no solo a un docker restart, sino a recrear el contenedor entero

Sobre paquetes: alpine (imagen alpine sin pin de versión, la misma que uso este setup) sí trae kubectl en sus repos — no hizo falta cambiar de imagen base ni instalarlo por binario aparte, contrario a lo que se sospechaba inicialmente. nodejs/npm (v24/v11 al momento de escribir esto) también están en los repos de alpine y cumplen de sobra el mínimo que pide Claude Code.

Nota sobre --network host + docker.sock: el contenedor tiene, en la práctica, control equivalente a root sobre el NAS (puede lanzar cualquier otro contenedor con cualquier mount). Es una concesión deliberada — es exactamente lo que hace falta para seguir operando Docker/k3d desde dentro — no un descuido.

Reconexión: un solo comando

/media/HDD-Storage/AppData/k3d-lab-v3/lab-cruzcloud/scripts/lab-session.sh

El script (scripts/lab-session.sh) es intencionalmente el mismo comando sin importar el estado previo:

docker exec -it "$CONTAINER" tmux attach -t "$SESSION" || \
  docker exec -it "$CONTAINER" tmux new -s "$SESSION"

Si la sesión lab existe, se adjunta a ella (mismo shell, mismo claude corriendo, mismo directorio de trabajo). Si no existe —por ejemplo justo después de que el contenedor se recreó desde cero— la crea. No hay que recordar cuál de los dos casos aplica.

Qué sobrevive a qué

Evento ¿Sobrevive la sesión tmux (lab)? ¿Sobrevive el login de Claude Code?
Corte de SSH / cerrar la terminal / cambiar de dispositivo Sí — es exactamente el caso que este setup resuelve Sí
docker restart tmux-host (reinicio del NAS) No — el proceso tmux server muere con el contenedor; lab-session.sh crea una sesión nueva automáticamente Sí, vía el volumen montado en /root/.claude
docker rm + recrear el contenedor desde cero No Sí, vía el mismo volumen — no hace falta volver a autenticar

Validación en vivo

Confirmado en este mismo lab (2026-08-27): se dejó una variable de entorno (export MARCA_PERSISTENCIA=ok_<timestamp>) corriendo dentro de la sesión lab vía tmux send-keys, y una invocación completamente separada de docker exec (equivalente a una nueva conexión) leyó de vuelta el mismo valor — confirma que el estado vive en el servidor tmux del contenedor, no en el cliente que se conecta. claude --version respondió 2.1.250 dentro de esa misma sesión, y un archivo de prueba escrito en /root/.claude dentro del contenedor apareció de inmediato en /DATA/AppData/k3d-lab-v3/claude-code-home en el host, confirmando el bind mount de credenciales.