Ejecutar Hermes Agent localmente en Podman sin root con Ollama en una GPU AMD
Ejecutar Hermes Agent localmente y de forma segura: Podman sin root, una carpeta montada, red interna, Ollama en una GPU AMD con ROCm, y una unidad systemd.
Fuentes: Run Hermes Agent Locally (and securely) with Ollama and Rootless Podman on Arch Linux
Ejecutar un agente de IA contra notas locales en Markdown y Obsidian parece sencillo hasta que surge la cuestión del acceso. Hermes Agent de Nous Research puede ejecutar comandos de terminal, editar archivos, ejecutar código y llamar a otras herramientas. Entregarle un modelo con instrucciones de acceder únicamente a una carpeta no crea una barrera de seguridad, ya que una instrucción es solo texto. La propia documentación de Hermes señala lo mismo: su parámetro HERMES_WRITE_SAFE_ROOT protege las herramientas habituales de edición de archivos, pero la herramienta de terminal sigue pudiendo acceder a todo lo que la cuenta del sistema operativo que ejecuta a Hermes puede alcanzar.
La configuración que se muestra a continuación coloca a Hermes dentro de un Podman sin raíz, junto a un servidor Ollama que ejecuta un modelo local en una GPU AMD. Hermes puede editar únicamente una carpeta que se monta intencionadamente. El resto del sistema de archivos del host simplemente no existe en su espacio de nombres de contenedor, por lo que no puede acceder a él. Hermes admite oficialmente su ejecución en un contenedor, mantiene el estado persistente en /opt/data y funciona con servidores de inferencia autohospedados como Ollama a través de un endpoint compatible con OpenAI.
La guía se realizó en EndeavourOS, una derivada de Arch. Ajuste las órdenes de los paquetes según sea necesario para otras distribuciones.
Instalación de Podman sin raíz
En Arch o EndeavourOS, instale Podman con Pacman:
sudo pacman -Syu podman
Pacman puede preguntar qué entorno de ejecución OCI utilizar, ofreciendo estas tres opciones:
1) crun
2) krun
3) runc
La opción elegida aquí es la primera:
1
Ese motor de ejecución es crun. Vale la pena más adelante, ya que la función keep-groups de Podman, que transfiere grupos de host adicionales a un contenedor sin raíz, funciona con crun.
Confirme la instalación imprimiendo la versión y preguntándole a Podman qué motor de ejecución utiliza:
podman --version
podman info --format '{{.Host.OCIRuntime.Name}}'
La segunda orden debe responder con el nombre en tiempo de ejecución:
crun
Los contenedores sin raíz también necesitan mapeos de UID y GID subordinados para el usuario, así que verifique que existan entradas en ambos archivos:
grep "^$USER:" /etc/subuid
grep "^$USER:" /etc/subgid
A partir de ahora, ejecute todos los comandos de Podman como el usuario normal. No los preceda con sudo.
Reparación de OverlayFS sin root cuando sea necesario
La primera descarga de imagen en esta máquina falló con un error que indicaba que el kernel no soportaba overlay en un sistema de archivos ext:
kernel does not support overlay fs:
'overlay' is not supported over extfs
Cuando eso ocurra, instale la implementación en el espacio de usuario:
sudo pacman -S fuse-overlayfs
Podman recomienda fuse-overlayfs para configuraciones sin root donde el overlay nativo no está disponible, y se puede especificar explícitamente como el programa de montaje sin root. Verifique que esté instalado:
which fuse-overlayfs
La ruta esperada es:
/usr/bin/fuse-overlayfs
La configuración se encuentra en un archivo por usuario:
~/.config/containers/storage.conf
Si ya existe, edítelo en lugar de reemplazarlo ciegamente. La sección de almacenamiento debe seleccionar el controlador de superposición:
[storage]
driver = "overlay"
Y una segunda sección debería indicar el nombre del programa de montaje:
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
Luego verifique la configuración efectiva filtrando la salida de depuración de Podman:
podman info --debug | grep -Ei 'graphDriverName|mount_program|overlay'
Mover el almacenamiento a una unidad más grande
En este proyecto, este paso se volvió necesario casi de inmediato. La imagen del contenedor Ollama por sí sola ocupa varios gigabytes; los modelos suelen añadir entre 5 y 20 GB o más, y Podman también utiliza espacio en disco temporal al descargar y desempaquetar las capas. La primera intentona de descargar Ollama finalizó con un mensaje indicando que no quedaba espacio en una ruta dentro del directorio temporal:
write /var/tmp/container_images_storage...:
no space left on device
Están involucradas dos ubicaciones separadas, lo cual es fácil de pasar por alto. El almacenamiento permanente sin raíz para imágenes, contenedores y volúmenes normalmente se encuentra debajo del directorio principal:
$HOME/.local/share/containers/storage
Las capas descargadas, sin embargo, se almacenan temporalmente en:
/var/tmp
Cambiar --tmpdir en Podman no reubica ese directorio de descargas. La documentación de Podman indica que se debe utilizar en su lugar la variable de entorno TMPDIR.
Con un sistema de archivos local más grande disponible, cree una estructura con un directorio para el almacenamiento y otro para los archivos temporales:
/path/to/large-drive/podman/
├── storage/
└── tmp/
Crear ambos y transferir la propiedad al usuario actual:
sudo mkdir -p /path/to/large-drive/podman/storage
sudo mkdir -p /path/to/large-drive/podman/tmp
sudo chown -R "$USER:$USER" /path/to/large-drive/podman
Luego abra la configuración de almacenamiento para editarla:
mkdir -p ~/.config/containers
nano ~/.config/containers/storage.conf
Dirija la raíz del gráfico al nuevo directorio de almacenamiento manteniendo el controlador de superposición:
[storage]
driver = "overlay"
graphroot = "/path/to/large-drive/podman/storage"
Y mantenga la configuración del programa de montaje fuse-overlayfs:
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
Elija un sistema de archivos local para esto. La documentación de Podman advierte que NFS y otros sistemas de archivos en red no funcionan correctamente como almacenamiento para contenedores sin root.
También es necesario reubicar las descargas temporales, así que establezca TMPDIR para la shell actual:
export TMPDIR=/path/to/large-drive/podman/tmp
Para que sea permanente en Bash, áñádalo al perfil y recárgalo:
echo 'export TMPDIR=/path/to/large-drive/podman/tmp' >> ~/.bashrc
source ~/.bashrc
Verifique las nuevas ubicaciones antes de eliminar cualquier cosa de la antigua. Imprima la raíz del gráfico y filtre la salida de depuración del directorio temporal y del programa de montaje:
podman info --format 'GraphRoot: {{.Store.GraphRoot}}'
podman info --debug | grep -Ei 'graphRoot|imageCopyTmpDir|mount_program'
Solo en una instalación reciente de Podman, y únicamente cuando la nueva raíz del grafo sea definitivamente correcta, se debe considerar la eliminación del almacenamiento abandonado en la ubicación antigua:
~/.local/share/containers/storage
No haga eso de forma casual en una instalación existente. También es por eso que se debe determinar la ubicación de almacenamiento antes de crear el volumen del modelo Ollama.
Un folder para Hermes, dos volúmenes para el estado
Elija el único directorio del host al que puede acceder Hermes, y hágalo de forma deliberada. Exporte su ruta en una variable y cree el folder:
export HERMES_WORKSPACE="$HOME/path/to/Hermes-Workspace"
mkdir -p "$HERMES_WORKSPACE"
Con Obsidian, una buena opción es utilizar una carpeta dedicada dentro de un vault, como un directorio de investigación situado junto a las carpetas personales y de trabajo:
Obsidian Vault/
├── Personal/
├── Work/
├── Research/
└── Agent Workspace/ ← only this folder is exposed
Todo lo demás en el cofre permanece invisible para el agente.
A continuación, cree dos volúmenes que Podman gestionará por sí mismo:
podman volume create hermes-data
podman volume create ollama-models
El primero almacena la configuración, la memoria y el estado persistente de Hermes. El segundo almacena el modelo local. Ninguno de los dos requiere exponer ningún otro directorio del host al agente.
Conceder acceso a la GPU AMD al contenedor
Esta configuración está dirigida a AMD ROCm, no a NVIDIA CUDA. En una tarjeta AMD, no instale nvidia-container-toolkit, un camino alternativo que se siguió anteriormente antes de darse cuenta del error. Las instrucciones de contenedor de Ollama utilizan la imagen ollama/ollama:rocm para GPUs AMD y pasan dos nodos de dispositivo al contenedor:
/dev/kfd
/dev/dri
Verifique que existan en el host:
ls -l /dev/kfd
ls -l /dev/dri/render*
Luego, mire los grupos de la cuenta:
groups
Si la cuenta falta en los grupos de video y renderizado, ágréguela:
sudo usermod -aG video,render "$USER"
Luego cierre sesión por completo y vuelva a iniciarla. Ejecutar nuevamente la lista del grupo en la misma terminal antigua no mostrará los nuevos miembros, lo que provoca cierta confusión innecesaria:
groups
Después de iniciar sesión nuevamente, ejecútelo una vez más:
groups
La salida ahora debe incluir a ambos grupos:
video render
La opción --group-add keep-groups de Podman transfiere posteriormente estos grupos adicionales al contenedor sin raíz para que pueda utilizar los dispositivos GPU.
Verificación del dispositivo TUN
Otra falla provino de las redes sin raíz. Pasta, el mecanismo de red sin raíz predeterminado de Podman, indicó que no podía abrir el dispositivo TUN:
pasta failed with exit code 1:
Failed to open() /dev/net/tun: No such device
Busque el nodo del dispositivo:
ls -l /dev/net/tun
Si está faltante, cargue el módulo del kernel:
sudo modprobe tun
Luego revísalo de nuevo. Si modprobe indica que el módulo no existe para el kernel en ejecución, y el sistema se actualizó recientemente, compara la versión del kernel en ejecución con las directorias de los módulos:
uname -r
ls /usr/lib/modules/
En una distribución basada en rolling release como Arch, es posible que la máquina siga ejecutando un kernel más antiguo cuyos módulos fueron eliminados durante la actualización. Reinicie y inténtelo de nuevo.
Elegir un modelo local
Para tarjetas con alrededor de 16 GB de VRAM, Ollama recomienda actualmente gemma4 como modelo local para Hermes, mientras que los sistemas con cerca de 24 GB pueden considerar modelos como qwen3.6. El ejemplo aquí utiliza una variante de gemma4 con 12 mil millones de parámetros:
gemma4:12b
Guárdelo en una variable, ajustando el modelo para el hardware real:
export HERMES_MODEL="gemma4:12b"
Otro parámetro importante es la longitud del contexto. Ollama configura por defecto las GPUs con menos de 24 GiB de VRAM para un contexto de solo 4K, mientras que ambos proyectos recomiendan un mínimo de 64,000 tokens cuando participan agentes. Esto se configura de forma explícita más adelante.
Obtención de imágenes y del modelo
Se necesitan tres imágenes: la imagen regular de Ollama, la versión ROCm y la imagen Hermes:
podman pull docker.io/ollama/ollama:latest
podman pull docker.io/ollama/ollama:rocm
podman pull docker.io/nousresearch/hermes-agent:latest
La imagen regular de Ollama se encarga de la descarga única del modelo. La imagen ROCm funciona como el servidor de inferencia permanente de AMD.
Descargue el modelo en su volumen persistente utilizando un contenedor temporal. Primero, elimine cualquier contenedor obsoleto:
podman rm -f ollama-bootstrap 2>/dev/null
Luego inicie el descargador con el volumen del modelo montado y nada más:
podman run -d \
--name ollama-bootstrap \
-v ollama-models:/root/.ollama \
docker.io/ollama/ollama:latest
Si la red de pasta sin raíz sigue sin funcionar, la red del anfitrión es aceptable solo para este descargador temporal. Elimine el contenedor nuevamente:
podman rm -f ollama-bootstrap 2>/dev/null
Y comiéncelo con la red del anfitrión:
podman run -d \
--network host \
--name ollama-bootstrap \
-v ollama-models:/root/.ollama \
docker.io/ollama/ollama:latest
No se ha montado ningún espacio de trabajo de Hermes en este contenedor. Extraiga el modelo a través de él:
podman exec -it ollama-bootstrap \
ollama pull "$HERMES_MODEL"
Confirme con un listado:
podman exec ollama-bootstrap ollama list
El modelo seleccionado debería aparecer:
gemma4:12b
Retire el contenedor temporal. El modelo permanece en el volumen:
podman rm -f ollama-bootstrap
Si Podman indica que el nombre del contenedor ya está en uso:
container name "ollama-bootstrap" is already in use
No sigas inventando nombres nuevos. En lugar de eso, limpia el contenedor obsoleto:
podman rm -f ollama-bootstrap
o añada la bandera --replace.
Una red interna y un pod
El primer diseño no utilizaba ninguna red:
--network none
Eso se cambió por un puente interno dedicado de Podman. Hermes y Ollama cuentan con un espacio de nombres de red compartido en el que pueden comunicarse entre sí, mientras que Podman restringe el acceso desde el exterior. La opción --internal existe específicamente para limitar el acceso externo en las redes de puente. Créela:
podman network create \
--ignore \
--internal \
hermes-internal
Luego cree el pod en esa red:
podman pod create \
--name hermes-local \
--network hermes-internal \
--userns=keep-id:uid=10000,gid=10000
La opción keep-id coloca la cuenta de host sin privilegios dentro del espacio de nombres de usuarios del contenedor, y los números que aparecen pueden elegirse. El contenedor Hermes se ejecuta con 10000:10000, y por eso aparecen esos valores.
Inicio de Ollama con ROCm
Aquí surgió uno de los problemas menos evidentes. La orden que finalmente funcionó es la siguiente:
podman run -d \
--name ollama \
--pod hermes-local \
--device /dev/kfd \
--device /dev/dri \
--group-add keep-groups \
-e HOME=/root \
-e OLLAMA_MODELS=/root/.ollama/models \
-e OLLAMA_CONTEXT_LENGTH=64000 \
-v ollama-models:/root/.ollama \
docker.io/ollama/ollama:rocm
Sus líneas específicas para AMD son:
--device /dev/kfd
--device /dev/dri
--group-add keep-groups
ollama/ollama:rocm
En este diseño de Podman, no se deben omitir dos variables de entorno:
HOME=/root
OLLAMA_MODELS=/root/.ollama/models
La razón es un fallo desconcertante. El modelo se había descargado correctamente, los archivos y el manifiesto existían y el volumen estaba montado adecuadamente. Sin embargo, al listar los modelos no se obtuvo ningún resultado:
podman exec ollama ollama list
El registro del contenedor contaba la historia:
podman logs ollama
Mostró a Ollama utilizando un directorio principal en la raíz del sistema de archivos y una ruta de modelos derivada de ella:
HOME=/
OLLAMA_MODELS=/.ollama/models
mientras que el modelo en realidad vivía en:
/root/.ollama/models
Ollama construye la ruta por defecto de su modelo a partir del directorio principal más .ollama/models, a menos que OLLAMA_MODELS lo sobrescriba. Por eso el modelo nunca faltó; Ollama buscaba en el lugar incorrecto. Al establecer explícitamente ambas variables, el problema se resolvió de inmediato:
HOME=/root
OLLAMA_MODELS=/root/.ollama/models
Ahora el listado debería mostrar el modelo:
podman exec ollama ollama list
Confirmación de que se está utilizando la GPU
Busque en el registro las líneas relacionadas con ROCm y la GPU:
podman logs ollama 2>&1 | grep -Ei 'gpu|rocm|amd|gfx'
Una configuración funcional de AMD identifica un dispositivo de cómputo ROCm. Luego se ejecuta una prueba rápida que solicita al modelo una respuesta exacta:
podman exec -it ollama \
ollama run gemma4:12b \
"Reply with exactly: AMD GPU test successful"
Inmediatamente después, observe los modelos en ejecución:
podman exec ollama ollama ps
Dos columnas merecen atención:
PROCESSOR
CONTEXT
Lo ideal es que la columna del procesador muestre la GPU y no la carga distribuida de la CPU, y el contexto es más o menos el siguiente:
64000
Ollama recomienda verificar su lista de procesos para ver tanto el contexto asignado como la descarga de recursos. Un modelo que indica tener un contexto nativo muy grande no necesariamente recibe esa cantidad asignada.
Inicio de Hermes con un montaje
Ahora cree el contenedor de Hermes dentro del mismo pod:
podman run -d \
--name hermes \
--pod hermes-local \
--security-opt=no-new-privileges \
--pids-limit 512 \
-v hermes-data:/opt/data \
-v "$HERMES_WORKSPACE:/opt/data/workspace:rw,nodev,nosuid" \
-w /opt/data/workspace \
docker.io/nousresearch/hermes-agent:latest \
sleep infinity
Lo importante es lo que falta en él. Ninguno de estos aparece:
/:/host
/home:/home
~/.ssh
~/.config
Docker socket
Podman socket
El único vínculo arbitrario de host es la carpeta de trabajo, que se mapea al directorio de datos del contenedor:
$HERMES_WORKSPACE
↓
/opt/data/workspace
El estado propio de Hermes se encuentra en el volumen especificado, que está montado en su ruta de datos:
hermes-data
↓
/opt/data
El contenedor oficial utiliza /opt/data para el estado persistente y establece automáticamente HERMES_WRITE_SAFE_ROOT en esa ubicación. Esa protección contra escrituras funciona como una medida adicional de seguridad, no como el sandbox principal, ya que la documentación de Hermes advierte que los comandos de terminal pueden eludir sus restricciones en las herramientas de archivos. El verdadero límite es el espacio de nombres del sistema de archivos de Podman.
Verificación del límite de montaje
Revise qué es lo que el host expone realmente a Hermes:
podman inspect hermes \
--format '{{range .Mounts}}{{println .Type .Source "->" .Destination}}{{end}}'
Esperar dos líneas, una para el volumen de datos y otra para la carpeta permitida:
Podman volume -> /opt/data
your allowed folder -> /opt/data/workspace
El directorio principal y el socket de container-runtime no deben aparecer. Verifique el socket explícitamente:
podman exec hermes sh -c \
'test ! -S /var/run/docker.sock && echo "No Docker socket exposed"'
También se pueden explorar rutas sensibles al anfitrión, por ejemplo, preguntando si un directorio SSH es visible:
podman exec hermes sh -lc \
'test -e "$HOME/.ssh" && echo "SSH directory visible" || echo "Host SSH directory not visible"'
Recuerde que el contenedor tiene sus propios directorios de sistema. Es normal verlos, ya que pertenecen al contenedor y no al host.
Hermes llegando a Ollama
Ambos contenedores comparten el espacio de nombres de red del pod, por lo que se comunican mediante loopback. Pruebe la API de Ollama compatible con OpenAI desde dentro del contenedor Hermes:
podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("http://127.0.0.1:11434/v1/models").read().decode())'
La respuesta debe ser un JSON que contenga el modelo local. Un primer intento utilizó un heredoc de Python y no imprimió nada:
podman exec hermes python - <<'PY'
...
PY
La causa era sencilla. Python lee su programa desde stdin en esa forma, pero a podman exec no se le indicó que conectara stdin. Al agregar la bandera interactiva, la versión heredoc funciona:
podman exec -i hermes python - <<'PY'
import urllib.request
print(
urllib.request.urlopen(
"http://127.0.0.1:11434/v1/models"
).read().decode()
)
PY
Esa bandera faltante costó unos minutos adicionales.
Demasiendo que el mundo exterior es inalcanzable
Intente obtener un sitio web externo desde el interior del contenedor de Hermes:
podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("https://example.com", timeout=5).read())'
Debería fallar, y eso es intencional. Hermes puede acceder a Ollama dentro de la cápsula, pero la red no permite acceso externo normal.
No entre en pánico si la lista de contenedores muestra:
podman ps
muestra un puerto junto a Ollama:
11434/tcp
Eso no significa que el puerto haya sido publicado en el host. Un puerto publicado se vería más o menos así:
0.0.0.0:11434->11434/tcp
No se utiliza -p 11434:11434 en ninguna parte de esta configuración.
Dirigir Hermes hacia Ollama local
Inicie la configuración de Hermes como el usuario del contenedor, trabajando en el espacio de trabajo:
podman exec -it \
--user 10000:10000 \
-w /opt/data/workspace \
hermes \
hermes model
El selector de proveedores muestra muchos servicios en la nube. No elija la opción de nube:
Ollama Cloud
Elige en su lugar la entrada del punto de extremo manual:
Custom endpoint (enter URL manually)
Para la URL base, utilice la dirección de bucle local y la ruta v1:
http://127.0.0.1:11434/v1
Si se solicita una clave API, déjela en blanco. Las notas actuales de integración de Hermes de Ollama utilizan exactamente este flujo de endpoint personalizado con una clave vacía para un servidor local. Cuando se pregunte sobre compatibilidad con API, elija la opción de completación de chat compatible con OpenAI. Para el nombre del modelo, ingrese:
gemma4:12b
Y en cuanto a la longitud del contexto:
64000
La documentación del proveedor de Hermes indica que Ollama local debe utilizar el endpoint v1 y necesita al menos 64K de contexto para los flujos de trabajo de agentes que utilizan herramientas.
Uso del backend de terminal local, dentro del contenedor
Al principio parece extraño. Normalmente Hermes advierte sobre la configuración de su backend de terminal local:
terminal:
backend: local
ejecuta comandos de terminal directamente en la misma máquina que Hermes, sin aislamiento alguno. En un Hermes instalado directamente, eso sería peligroso. Aquí, Hermes en sí ya se encuentra dentro de Podman, por lo que el backend local se resuelve al contenedor de Hermes:
Hermes "local"
↓
Hermes container
y no a la estación de trabajo:
Hermes "local"
↓
your Arch workstation
Establecer el backend:
podman exec --user 10000:10000 hermes \
hermes config set terminal.backend local
Y el directorio de trabajo:
podman exec --user 10000:10000 hermes \
hermes config set terminal.cwd /opt/data/workspace
También vale la pena establecer una preferencia por un modo de inicio basado en perfiles:
podman exec --user 10000:10000 hermes \
hermes config set terminal.home_mode profile
No configure el backend Docker de Hermes para este diseño. Esto requeriría otorgarle a Hermes acceso a un entorno de ejecución de contenedores, lo cual anula una de las principales decisiones de aislamiento.
Arrancar Hermes
Una sesión interactiva comienza con el mismo patrón de ejecución utilizado para la configuración:
podman exec -it \
--user 10000:10000 \
-w /opt/data/workspace \
hermes \
hermes
Para la limpieza de notas, se podría pedir a Hermes que revise las notas en Markdown del espacio de trabajo, organice la estructura y los encabezados, corrija el formato y la gramática, mantenga intactos todos los detalles técnicos, comandos, enlaces y enlaces wiki de Obsidian, y nunca elimine contenido real solo para acortar una nota. Los cambios se aplican directamente en la única carpeta del host montada.
Inicio del entorno al arranque
Una vez que todo funcione de forma interactiva, el pod debería volver a funcionar después de un reinicio sin necesidad de iniciar manualmente Ollama y Hermes. Cree una unidad systemd para el usuario:
mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/hermes-local.service
La unidad comienza con una descripción y una línea de pedido:
[Unit]
Description=Hermes Local AI Pod
After=default.target
La sección de servicio es un proceso único que inicia el pod, lo detiene con un período de gracia de 30 segundos y permite dos minutos para que se reinicie:
[Service]
Type=oneshot
RemainAfterExit=yesExecStart=/usr/bin/podman pod start hermes-local
ExecStop=/usr/bin/podman pod stop -t 30 hermes-localTimeoutStartSec=120
TimeoutStopSec=60[Install]
WantedBy=default.target
Si la raíz del grafo de Podman se encuentra en una unidad separada, dicha unidad debe montarse de forma fiable al arrancar y no ser algo que el escritorio monte de forma interactiva después del inicio de sesión. Se puede añadir una verificación de montaje opcional ExecStartPre adaptada al sistema de archivos.
Volver a cargar systemd:
systemctl --user daemon-reload
Habilitar la unidad:
systemctl --user enable hermes-local.service
Inícialo:
systemctl --user start hermes-local.service
Y revisa esto:
systemctl --user status hermes-local.service
Dado que se trata de un servicio de un solo uso con RemainAfterExit activado, este estado es normal:
Active: active (exited)
En Podman existe un mecanismo Quadlet más reciente para definir contenedores de forma declarativa bajo systemd. Sin embargo, cuando el entorno ya está preparado y probado manualmente, un pequeño servicio de usuario que inicia el pod y que se ha demostrado funcional causa la menor interrupción.
Para permitir que el servicio de usuario sin privilegios de root se inicie al arrancar, incluso antes de iniciar sesión en el escritorio, active la opción lingering:
sudo loginctl enable-linger "$USER"
Verifíquelo:
loginctl show-user "$USER" -p Linger
El resultado esperado:
Linger=yes
La función linger de Systemd hace que el gestor de servicios del usuario se inicie al arrancar y permanezca disponible fuera de las sesiones normales de inicio de sesión. Después de un reinicio, la lista de contenedores:
podman ps
Ya debería mostrarse el pod con Ollama y Hermes en ejecución. Una sesión interactiva de Hermes sigue iniciándose con el comando exec:
podman exec -it \
--user 10000:10000 \
-w /opt/data/workspace \
hermes \
hermes
El servicio de arranque inicia el entorno. No abre un chat interactivo.
Problemas que realmente ocurrieron
Aquí está la lista resumida de los fallos encontrados en el proceso, cada uno como un problema y su solución.
Un error de superposición del kernel:
Problem:
kernel does not support overlay fs
Se resolvió instalando fuse-overlayfs y configurándolo como el programa de montaje overlay:
Fix:
Install fuse-overlayfs and configure it as the overlay mount program.
Se está agotando el espacio en el directorio temporal:
Problem:
no space left on device under /var/tmp
Se necesitaba tanto un origen del grafo movido cuando era apropiado como una configuración de TMPDIR, ya que la bandera tmpdir de Podman es algo distinto:
Fix:
Move Podman's graphroot if needed AND set TMPDIR.
Changing Podman's --tmpdir is not the same thing.
Hay un conflicto de nombres para el contenedor bootstrap:
Problem:
ollama-bootstrap name already in use
se soluciona eliminando el contenedor obsoleto:
Fix:
podman rm -f ollama-bootstrap
El programa Pasta no logra abrir el dispositivo TUN:
Problem:
pasta cannot open /dev/net/tun
se soluciona cargando el módulo tun, o reiniciando si los módulos instalados no coinciden con el kernel en ejecución:
Fix:
sudo modprobe tun
If the installed modules don't match the running kernel, reboot.
Un kit de herramientas de NVIDIA instalado en una máquina AMD:
Problem:
Installed nvidia-container-toolkit on an AMD machine
Simplemente no debería hacerse; en su lugar, use la imagen ROCm con los dispositivos kfd y dri:
Fix:
Don't.
Use ollama/ollama:rocm with /dev/kfd and /dev/dri.
Nuevas membresías en grupos de video y renderizado que no aparecían en la lista de grupos:
Problem:
Added myself to video/render but `groups` still didn't show them
Se necesita un cierre de sesión y un inicio de sesión, ya que la sesión existente mantiene sus grupos originales:
Fix:
Log out and back in.
The existing login session retains its original supplementary groups.
Los archivos del modelo y el manifiesto están presentes mientras que la lista de Ollama está vacía:
Problem:
Ollama model files and manifest exist, but `ollama list` is empty
se diagnostica al leer el registro, y si las rutas de home y modelos apuntan a la raíz del sistema de archivos mientras el volumen está montado en el directorio del usuario root, es necesario establecer explícitamente ambas variables:
Fix:
Check:
podman logs ollamaIf Ollama is using:
OLLAMA_MODELS=/.ollama/modelswhile the volume is mounted under:
/root/.ollamaset explicitly:
HOME=/root
OLLAMA_MODELS=/root/.ollama/models
Una prueba en Python de Hermes a Ollama que imprimió silenciosamente nada:
Problem:
Hermes → Ollama Python test silently prints nothing
Se solucionó al agregar la bandera interactiva al usar un heredoc, o simplemente utilizando el formulario de comando en línea:
Fix:
If using `python -` with a heredoc, add `podman exec -i`.
Or simply use `python -c`.
Un menú de proveedores sin una entrada local de Ollama evidente:
Problem:
The Hermes provider menu doesn't contain "local Ollama"
Se resolvió seleccionando el endpoint personalizado manual y ingresando la URL local v1:
Fix:
Choose:
Custom endpoint (enter URL manually)Then:
http://127.0.0.1:11434/v1
Y Hermes quejándose de una ventana de contexto insuficiente, aunque todo lo demás funcionaba bien:
Problem:
Everything works, but Hermes reports an inadequate context window
se resuelve estableciendo la longitud de contexto del lado del servidor en 64000, configurando Hermes con el mismo valor y verificando la asignación real mediante la lista de procesos de Ollama:
Fix:
Set OLLAMA_CONTEXT_LENGTH=64000 server-side and configure Hermes for the same value.
Verify the real allocation with:
ollama ps
Dónde debe ubicarse el límite de confianza
La lección principal aquí no se refiere realmente a Ollama ni a ROCm, sino a decidir dónde se encuentra el límite de confianza. Una instrucción que dice nunca leer nada fuera de un directorio es una directiva de comportamiento. Un contenedor Podman al que nunca se le ha proporcionado el resto del sistema de archivos del host representa un límite del sistema operativo. La raíz segura para escritura de Hermes es útil, pero Hermes mismo la describe como una capa adicional y no como una verdadera caja de arena, ya que los comandos de la shell pueden eludir la protección de las herramientas de archivos.
El modelo de confianza resultante apila las capas de la más débil a la más fuerte, comenzando con las restricciones en las instrucciones y la protección contra escritura de archivos de Hermes, y terminando con el límite del contenedor:
Prompt restrictions
↓
Hermes file write protections
↓
Hermes container filesystem
↓
Rootless Podman user namespace
↓
Host filesystem permissions
Incluso si el modelo recibe una instrucción maliciosa oculta en un archivo Markdown, la pregunta importante no es si obedecerá la solicitud original, sino a qué puede acceder realmente el agente comprometido. En este diseño, la respuesta es intencionadamente limitada.
Depuración por capas
Tanto Hermes como Ollama evolucionan rápidamente. La documentación actual de Hermes admite endpoints compatibles con OpenAI autohospedados, incluido Ollama, pero también ha habido informes sobre el comportamiento de las llamadas a herramientas a través de endpoints personalizados de Ollama. Si el chat normal funciona pero Hermes parece describir las llamadas a herramientas sin ejecutarlas, revise el registro de problemas actual de Hermes antes de asumir que la sandbox de Podman está dañada.
Esa es la razón para probar la pila como una cadena de preguntas: desde si Podman detecta la GPU, pasando por si Ollama puede acceder al modelo, hasta determinar si Hermes puede comunicarse con Ollama.
Does Podman see the GPU?
↓
Does Ollama see the model?
↓
Does Ollama actually use the GPU?
↓
Is the context really 64K?
↓
Can Hermes reach /v1/models?
↓
Can Hermes perform an actual file operation?
↓
Can Hermes reach anything it shouldn't?
No intente depurar las seis capas al mismo tiempo.
Qué ofrece la configuración final
La configuración resultante permite la inferencia local, la aceleración por GPU AMD, memoria Hermes persistente y un espacio de trabajo del host escribible:
Local inference YES
AMD GPU acceleration YES
Hermes persistent memory YES
One writable host workspace YES
Y no necesita ningún modelo en la nube, no expone ningún sistema de archivos del host, no expone ningún socket de Podman ni Docker y no tiene salida a Internet habitual:
Cloud LLM required NO
Host filesystem exposed NO
Podman/Docker socket exposed NO
Normal Internet egress NO
Entire Obsidian vault exposed NO
Esa es una forma mucho más cómoda de experimentar con un agente local capaz. Hermes todavía tiene suficiente acceso como para ser útil. Simplemente no puede decidir por sí mismo qué significa tener acceso suficiente.