Exécuter l’agent Hermes localement dans Podman sans root avec Ollama sur une GPU AMD
Exécuter Hermes Agent localement et en toute sécurité : Podman sans accès root, une seule carte mémoire montée, réseau interne, Ollama sur une GPU AMD avec ROCm, ainsi qu’une unité systemd.
Sources: Run Hermes Agent Locally (and securely) with Ollama and Rootless Podman on Arch Linux
Exécuter un agent IA sur des notes Markdown et Obsidian locales semble simple jusqu’à ce que la question de l’accès se pose. Hermes Agent de Nous Research peut exécuter des commandes de terminal, modifier des fichiers, exécuter du code et appeler d’autres outils. Fournir à cet agent un modèle qui ne doit accéder qu’à un seul dossier ne crée pas de barrière de sécurité, car une instruction n’est qu’un texte. La documentation officielle d’Hermes souligne le même point : sa configuration HERMES_WRITE_SAFE_ROOT protège les outils normaux de modification de fichiers, mais l’outil de terminal peut toujours accéder à tout ce à quoi le compte du système d’exploitation sur lequel fonctionne Hermes a accès.
La configuration présentée ci-dessous place Hermes directement à l’intérieur de Podman sans accès au système racine, à côté d’un serveur Ollama qui exécute un modèle local sur une GPU AMD. Hermes ne peut modifier qu’un seul dossier monté intentionnellement. Le reste du système de fichiers hôte est simplement absent de son espace de noms de conteneur, ce qui l’empêche d’y accéder. Hermes prend officiellement en charge son exécution dans un conteneur, conserve un état persistant sous /opt/data, et fonctionne avec des serveurs d’inférence auto-hébergés tels qu’Ollama via une interface compatible OpenAI.
Cette démarche a été réalisée sur EndeavourOS, une dérivée d’Arch. Adaptez les commandes de paquets à d’autres distributions.
Installation de Podman sans accès au système racine
Sur Arch ou EndeavourOS, installez Podman à l’aide de Pacman :
sudo pacman -Syu podman
Pacman peut demander quel environnement de exécution OCI utiliser, en proposant ces trois options :
1) crun
2) krun
3) runc
Le choix effectué ici est le premier :
1
Ce runtime est crun. Cela s’avère utile par la suite, car la fonction keep-groups de Podman, qui transmet des groupes hôtes supplémentaires à un conteneur sans racine, fonctionne avec crun.
Vérifiez l’installation en affichant la version et en demandant à Podman quel runtime il utilise :
podman --version
podman info --format '{{.Host.OCIRuntime.Name}}'
La deuxième commande doit répondre par le nom en temps de exécution :
crun
Les conteneurs sans racine ont également besoin de mappages UID et GID subordonnés pour l’utilisateur, donc vérifiez que des entrées existent dans les deux fichiers :
grep "^$USER:" /etc/subuid
grep "^$USER:" /etc/subgid
Exécutez à partir de maintenant tous les commandes Podman en tant qu’utilisateur normal. Ne les préfixez pas de sudo.
Réparation d’OverlayFS sans root en cas de besoin
Le premier téléchargement d’image sur cette machine a échoué en raison d’une erreur indiquant que le noyau ne prenait pas en charge Overlay sur un système de fichiers ext :
kernel does not support overlay fs:
'overlay' is not supported over extfs
Lorsque cela se produit, installez l’implémentation en espace utilisateur :
sudo pacman -S fuse-overlayfs
Podman recommande fuse-overlayfs pour les configurations sans root où l’overlay natif n’est pas disponible, et il peut être spécifiquement désigné comme le programme de montage sans root. Vérifiez qu’il est installé :
which fuse-overlayfs
Le chemin attendu est :
/usr/bin/fuse-overlayfs
La configuration est stockée dans un fichier par utilisateur :
~/.config/containers/storage.conf
Si il existe déjà, modifiez-le plutôt que de le remplacer aveuglément. La section de stockage doit sélectionner le pilote d’overlay :
[storage]
driver = "overlay"
Une deuxième section doit indiquer le nom du programme de montage :
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
Ensuite, vérifiez les paramètres en vigueur en filtrant les sorties de débogage de Podman :
podman info --debug | grep -Ei 'graphDriverName|mount_program|overlay'
Déplacer le stockage vers un disque plus grand
Dans le cadre de ce projet, cette étape est devenue nécessaire presque immédiatement. L’image de conteneur Ollama seule occupe déjà plusieurs gigaoctets, les modèles ajoutent souvent entre 5 et 20 Go, voire plus, et Podman utilise également de l’espace disque temporaire lors du téléchargement et du déballage des couches. La première tentative de récupération d’Ollama a fini par échouer avec un message indiquant qu’il n’y avait plus d’espace dans un répertoire situé sous le dossier temporaire :
write /var/tmp/container_images_storage...:
no space left on device
Deux emplacements distincts sont impliqués, ce qui peut facilement être négligé. Le stockage permanent sans racine pour les images, les conteneurs et les volumes se trouve généralement sous le répertoire home :
$HOME/.local/share/containers/storage
Les couches téléchargées se trouvent temporairement dans :
/var/tmp
Changer la valeur de --tmpdir chez Podman ne déplace pas ce répertoire de téléchargement. La documentation de Podman indique d’utiliser plutôt la variable d’environnement TMPDIR.
Avec un système de fichiers local plus volumineux, créez une structure comprenant un répertoire pour le stockage et un autre pour les fichiers temporaires :
/path/to/large-drive/podman/
├── storage/
└── tmp/
Créer les deux et transférer la propriété à l’utilisateur actuel :
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
Ensuite, ouvrez la configuration du stockage pour la modifier :
mkdir -p ~/.config/containers
nano ~/.config/containers/storage.conf
Pointez la racine du graphique vers le nouveau répertoire de stockage tout en conservant le pilote d’overlay :
[storage]
driver = "overlay"
graphroot = "/path/to/large-drive/podman/storage"
Et conservez la configuration de montage fuse-overlayfs :
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
Choisissez un système de fichiers local pour cela. La documentation de Podman met en garde contre le fait que NFS et d’autres systèmes de fichiers réseau se comportent de manière incorrecte en tant que stockage pour des conteneurs sans root.
Les téléchargements temporaires doivent également être déplacés, il faut donc définir TMPDIR pour le shell actuel :
export TMPDIR=/path/to/large-drive/podman/tmp
Pour le rendre permanent dans Bash, ajoutez-le au profil et rechargez-le :
echo 'export TMPDIR=/path/to/large-drive/podman/tmp' >> ~/.bashrc
source ~/.bashrc
Vérifiez les nouvelles localisations avant de supprimer quoi que ce soit de l’ancienne. Imprimez la racine du graphe et filtrez les résultats de débogage relatifs au répertoire temporaire et au programme de montage :
podman info --format 'GraphRoot: {{.Store.GraphRoot}}'
podman info --debug | grep -Ei 'graphRoot|imageCopyTmpDir|mount_program'
Seul sur une installation fraîche de Podman, et uniquement lorsque la nouvelle racine du graphe est définitivement correcte, le stockage abandonné à l’ancienne emplacement devrait être envisagé pour suppression :
~/.local/share/containers/storage
Ne faites pas cela à la légère sur une installation existante. C’est aussi pour cette raison qu’il convient de déterminer l’emplacement de stockage avant de créer le volume du modèle Ollama.
Un dossier pour Hermes, deux volumes pour l’état
Sélectionnez le seul répertoire hôte que Hermes pourra modifier, et choisissez-le en connaissance de cause. Exportez son chemin dans une variable et créez le dossier :
export HERMES_WORKSPACE="$HOME/path/to/Hermes-Workspace"
mkdir -p "$HERMES_WORKSPACE"
Avec Obsidian, une bonne option consiste à utiliser un dossier dédié à l’intérieur d’un coffre-fort, comme un répertoire de recherche situé à côté des dossiers personnels et professionnels :
Obsidian Vault/
├── Personal/
├── Work/
├── Research/
└── Agent Workspace/ ← only this folder is exposed
Tout le reste dans le coffre-fort reste invisible pour l’agent.
Ensuite, créez deux volumes que Podman gérera lui-même :
podman volume create hermes-data
podman volume create ollama-models
Le premier contient la configuration, la mémoire et l’état persistant d’Hermes. Le second contient le modèle local. Aucun des deux n’exige que des dossiers hôtes supplémentaires soient mis à disposition de l’agent.
Donner accès à la GPU AMD au conteneur
Cette configuration vise AMD ROCm, et non NVIDIA CUDA. Sur une carte AMD, ne téléchargez pas nvidia-container-toolkit, solution de contournement utilisée précédemment avant que l’erreur ne soit détectée. Les instructions de conteneur d’Ollama utilisent l’image ollama/ollama:rocm pour les GPU AMD et transmettent deux nœuds de périphériques au conteneur :
/dev/kfd
/dev/dri
Vérifiez qu’ils existent sur l’hôte :
ls -l /dev/kfd
ls -l /dev/dri/render*
Ensuite, consultez les groupes du compte :
groups
Si le compte manque dans les groupes de vidéo et de rendu, ajoutez-le :
sudo usermod -aG video,render "$USER"
Ensuite, déconnectez-vous complètement puis reconnectez-vous. L’exécution à nouveau de la liste des groupes dans le même terminal ancien ne montrera pas les nouveaux membres, ce qui provoque des perplexités inutiles :
groups
Après un nouveau connexion, exécutez-le à nouveau :
groups
La sortie doit maintenant inclure les deux groupes :
video render
L’option --group-add keep-groups de Podman transmet ensuite ces groupes supplémentaires au conteneur sans racine afin qu’il puisse utiliser les dispositifs GPU.
Vérification du dispositif TUN
Un autre échec est survenu en raison du réseau sans racine. Pasta, le mécanisme de réseau sans racine par défaut de Podman, a indiqué qu’il ne pouvait pas ouvrir le dispositif TUN :
pasta failed with exit code 1:
Failed to open() /dev/net/tun: No such device
Recherchez le nœud du dispositif :
ls -l /dev/net/tun
Si il est manquant, chargez le module du noyau :
sudo modprobe tun
Vérifiez à nouveau. Si modprobe indique que le module n’existe pas pour le noyau en cours d’exécution, et que le système a été mis à jour récemment, comparez la version du noyau en cours d’exécution avec les répertoires des modules :
uname -r
ls /usr/lib/modules/
Dans une distribution itérative comme Arch, l’ordinateur peut encore exécuter un noyau plus ancien dont les modules ont été supprimés lors de la mise à niveau. Redémarrez et réessayez.
Choix d’un modèle local
Pour les cartes disposant d’environ 16 Go de VRAM, Ollama recommande actuellement gemma4 comme modèle local pour Hermes, tandis que les systèmes ayant près de 24 Go peuvent envisager des modèles tels que qwen3.6. L’exemple présenté ici utilise une version de gemma4 comptant 12 milliards de paramètres :
gemma4:12b
Stockez-le dans une variable, en ajustant le modèle pour le matériel réel :
export HERMES_MODEL="gemma4:12b"
Un autre paramètre important est la longueur du contexte. Par défaut, Ollama limite les GPU disposant de moins de 24 Go de VRAM à un contexte de 4K, tandis que les deux projets recommandent au moins 64 000 tokens lorsque des agents sont impliqués. Cette valeur peut être configurée explicitement plus loin.
Chargement d’images et du modèle
Trois images sont nécessaires : l’image Ollama standard, la version ROCm et l’image Hermes :
podman pull docker.io/ollama/ollama:latest
podman pull docker.io/ollama/ollama:rocm
podman pull docker.io/nousresearch/hermes-agent:latest
L’image Ollama standard gère le téléchargement unique du modèle. L’image ROCm devient le serveur d’inférence AMD permanent.
Téléchargez le modèle dans son volume persistant à l’aide d’un conteneur temporaire. Veuillez d’abord supprimer tout conteneur obsolète :
podman rm -f ollama-bootstrap 2>/dev/null
Ensuite, lancez le téléchargeur avec le volume du modèle monté, et rien d’autre :
podman run -d \
--name ollama-bootstrap \
-v ollama-models:/root/.ollama \
docker.io/ollama/ollama:latest
Si le réseau de pâtes sans racine refuse toujours de fonctionner, le réseau hôte est acceptable uniquement pour ce téléchargeur temporaire. Supprimez à nouveau le conteneur :
podman rm -f ollama-bootstrap 2>/dev/null
Et commencez par le réseau hôte :
podman run -d \
--network host \
--name ollama-bootstrap \
-v ollama-models:/root/.ollama \
docker.io/ollama/ollama:latest
Aucun espace de travail Hermes n’est monté dans ce conteneur. Téléchargez le modèle à travers celui-ci :
podman exec -it ollama-bootstrap \
ollama pull "$HERMES_MODEL"
Confirmer avec une fiche :
podman exec ollama-bootstrap ollama list
Le modèle sélectionné doit apparaître :
gemma4:12b
Retirez le conteneur temporaire. Le modèle reste dans le volume :
podman rm -f ollama-bootstrap
Si Podman signale que le nom du conteneur est déjà pris :
container name "ollama-bootstrap" is already in use
Ne continuez pas à inventer de nouveaux noms. Veuillez plutôt vider le conteneur obsolète :
podman rm -f ollama-bootstrap
ou ajoutez le flag --replace.
Un réseau interne et un pod
La première conception ne utilisait aucun réseau du tout :
--network none
Cela a été modifié pour utiliser un pont Podman interne dédié. Hermes et Ollama disposent d’un espace de noms réseau partagé leur permettant de communiquer entre eux, tandis que Podman restreint l’accès depuis l’extérieur. L’option --internal existe spécifiquement pour limiter l’accès externe sur les réseaux de type pont. Créez-le :
podman network create \
--ignore \
--internal \
hermes-internal
Créez ensuite le pod sur ce réseau :
podman pod create \
--name hermes-local \
--network hermes-internal \
--userns=keep-id:uid=10000,gid=10000
L’option keep-id place le compte hôte non privilégié dans l’espace de noms des utilisateurs du conteneur, et les nombres qui y apparaissent peuvent être choisis. Le conteneur Hermes s’exécute avec les identifiants 10000:10000, c’est pourquoi ces valeurs apparaissent.
Démarrer Ollama avec ROCm
L’un des problèmes moins évidents est apparu ici. La commande qui a finalement fonctionné ressemble à ceci :
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
Ses lignes spécifiques à AMD sont :
--device /dev/kfd
--device /dev/dri
--group-add keep-groups
ollama/ollama:rocm
Dans cette configuration Podman, deux variables d’environnement ne doivent pas être omises :
HOME=/root
OLLAMA_MODELS=/root/.ollama/models
La raison en est une défaillance énigmatique. Le modèle avait été téléchargé correctement, les fichiers et le manifeste existaient, et le volume était monté comme il se doit. Pourtant, la liste des modèles ne renvoyait absolument rien :
podman exec ollama ollama list
Le journal du conteneur racontait l’histoire :
podman logs ollama
Cela montrait Ollama utilisant un répertoire personnel situé à la racine du système de fichiers, ainsi qu’un chemin pour les modèles dérivé de celui-ci :
HOME=/
OLLAMA_MODELS=/.ollama/models
alors que le modèle vivait réellement à :
/root/.ollama/models
Ollama construit son chemin par défaut pour le modèle à partir du répertoire principal ainsi que de .ollama/models, à moins que OLLAMA_MODELS ne le modifie. Ainsi, le modèle n’a jamais manqué : Ollama cherchait simplement dans le mauvais endroit. En définissant explicitement les deux variables, le problème a été résolu sur-le-champ :
HOME=/root
OLLAMA_MODELS=/root/.ollama/models
Désormais, la fiche devrait afficher le modèle :
podman exec ollama ollama list
Vérifier que la GPU est utilisée
Recherchez dans le journal les lignes concernant ROCm et la GPU :
podman logs ollama 2>&1 | grep -Ei 'gpu|rocm|amd|gfx'
Une configuration AMD fonctionnelle identifie un dispositif de calcul ROCm. Ensuite, exécutez une commande de test rapide qui demande au modèle de fournir une réponse exacte :
podman exec -it ollama \
ollama run gemma4:12b \
"Reply with exactly: AMD GPU test successful"
Immédiatement après, consultez les modèles en cours d’exécution :
podman exec ollama ollama ps
Deux colonnes méritent une attention particulière :
PROCESSOR
CONTEXT
Idéalement, la colonne du processeur afficherait le GPU plutôt que le transfert de charge vers le CPU, et le contexte concerne :
64000
Ollama recommande lui-même de vérifier la liste de ses processus afin de voir à la fois le contexte alloué et le téléchargement externe. Un modèle qui affiche un contexte natif très important n’obtient pas nécessairement autant de mémoire allouée.
Démarrer Hermes avec un seul montage
Créez maintenant le conteneur Hermes à l’intérieur du même 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
Ce qui compte, c’est ce qui lui manque. Aucun de ces éléments n’apparaît :
/:/host
/home:/home
~/.ssh
~/.config
Docker socket
Podman socket
Le seul paramètre d’association d’hôte arbitraire est le dossier de travail, qui est mappé dans le répertoire de données du conteneur :
$HERMES_WORKSPACE
↓
/opt/data/workspace
L’état propre d’Hermes se trouve dans le volume nommé, qui est monté sur son chemin de données :
hermes-data
↓
/opt/data
Le conteneur officiel utilise /opt/data pour le stockage persistant et définit automatiquement HERMES_WRITE_SAFE_ROOT sur cette cheminée. Cette protection contre les écritures constitue une mesure de défense en profondeur, et non le sandbox principal, car la documentation d’Hermes met en garde contre le fait que les commandes de terminal peuvent contourner ses restrictions relatives aux outils de fichiers. La véritable limite est l’espace de noms du système de fichiers de Podman.
Vérification de la limite de montage
Examinez ce que l’hôte expose réellement à Hermes :
podman inspect hermes \
--format '{{range .Mounts}}{{println .Type .Source "->" .Destination}}{{end}}'
Attendez deux lignes, une pour le volume de données et une pour le dossier autorisé :
Podman volume -> /opt/data
your allowed folder -> /opt/data/workspace
Le répertoire home et le socket de container-runtime ne doivent pas apparaître. Vérifiez explicitement le socket :
podman exec hermes sh -c \
'test ! -S /var/run/docker.sock && echo "No Docker socket exposed"'
Les chemins sensibles à l’hôte peuvent également être vérifiés, par exemple en demandant si un répertoire SSH est visible :
podman exec hermes sh -lc \
'test -e "$HOME/.ssh" && echo "SSH directory visible" || echo "Host SSH directory not visible"'
N’oubliez pas que le conteneur dispose de ses propres répertoires système. Il est normal de les voir, car ils appartiennent au conteneur et non à l’hôte.
Hermes atteint Ollama
Les deux conteneurs partagent l’espace de noms réseau du pod, ce qui leur permet de communiquer via un routage circulaire. Testez l’API compatible OpenAI d’Ollama depuis l’intérieur du conteneur Hermes :
podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("http://127.0.0.1:11434/v1/models").read().decode())'
La réponse doit être un JSON contenant le modèle local. Une première tentative a utilisé un heredoc en Python sans afficher rien :
podman exec hermes python - <<'PY'
...
PY
La cause était simple. Python lit son programme depuis stdin sous cette forme, mais podman exec n’avait pas reçu l’instruction d’associer stdin. L’ajout du flag interactif fait fonctionner la version heredoc :
podman exec -i hermes python - <<'PY'
import urllib.request
print(
urllib.request.urlopen(
"http://127.0.0.1:11434/v1/models"
).read().decode()
)
PY
Ce drapeau manquant a coûté quelques minutes supplémentaires.
Prouver que le monde extérieur est inatteignable
Essayez de récupérer un site web externe depuis l’intérieur du conteneur d’Hermes :
podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("https://example.com", timeout=5).read())'
Cela doit échouer, et c’est intentionnel. Hermes peut atteindre Ollama à l’intérieur du conteneur, mais le réseau ne permet aucun accès externe normal.
Ne paniquez pas si la liste des conteneurs affiche :
podman ps
affiche un port à côté d’Ollama :
11434/tcp
Cela ne signifie pas que le port a été publié sur l’hôte. Un port publié ressemblerait plutôt à ceci :
0.0.0.0:11434->11434/tcp
Aucune occurrence de -p 11434:11434 n’est utilisée dans cette configuration.
Diriger Hermes vers Ollama local
Démarrez la configuration de Hermes en tant qu’utilisateur du conteneur, dans l’espace de travail :
podman exec -it \
--user 10000:10000 \
-w /opt/data/workspace \
hermes \
hermes model
Le sélecteur de fournisseur liste de nombreux services cloud. Ne choisissez pas l’option cloud :
Ollama Cloud
Choisissez plutôt l’entrée d’endpoint manuel :
Custom endpoint (enter URL manually)
Pour l’URL de base, utilisez l’adresse de boucle locale et le chemin v1 :
http://127.0.0.1:11434/v1
Si une clé API est demandée, laissez-la vide. Les notes actuelles d’intégration d’Hermes d’Ollama utilisent précisément ce flux de point de terminaison personnalisé avec une clé vide pour un serveur local. Lorsqu’on vous demande à propos de la compatibilité API, choisissez l’option de complétion de chat compatible avec OpenAI. Pour le nom du modèle, saisissez :
gemma4:12b
Et pour la longueur du contexte :
64000
La documentation du fournisseur d’Hermes indique que l’Ollama local doit utiliser l’endpoint v1 et a besoin d’au moins 64 K de contexte pour les workflows d’agent qui utilisent des outils.
Utilisation du backend de terminal local, à l’intérieur du conteneur
Cela peut sembler étrange au premier abord. Hermes avertit normalement que sa configuration de backend de terminal local :
terminal:
backend: local
exécute des commandes de terminal directement sur la même machine que Hermes, sans aucune isolation. Sur une installation directe de Hermes, cela serait dangereux. Ici, Hermes est déjà intégré à Podman, de sorte que le backend local fait référence au conteneur Hermes :
Hermes "local"
↓
Hermes container
et non à l’ordinateur de travail :
Hermes "local"
↓
your Arch workstation
Définir le backend :
podman exec --user 10000:10000 hermes \
hermes config set terminal.backend local
Et le répertoire de travail :
podman exec --user 10000:10000 hermes \
hermes config set terminal.cwd /opt/data/workspace
Il est également utile de définir une préférence pour un mode d’accueil basé sur le profil :
podman exec --user 10000:10000 hermes \
hermes config set terminal.home_mode profile
Ne configurez pas le backend Docker d’Hermes pour cette conception. Cela nécessiterait de donner à Hermes un accès à un environnement d’exécution de conteneurs, ce qui annulerait l’une des principales décisions en matière d’isolation.
Démarrer Hermes
Une session interactive débute avec le même schéma d’exécution utilisé pour la configuration :
podman exec -it \
--user 10000:10000 \
-w /opt/data/workspace \
hermes \
hermes
Afin de nettoyer les notes, une instruction pourrait demander à Hermes d’examiner les notes Markdown présentes dans l’espace de travail, de mettre de l’ordre dans la structure et les titres, de corriger la mise en forme et la grammaire, de conserver tous les détails techniques, commandes, liens et liens wikilink d’Obsidian tels quels, et de ne jamais supprimer du contenu réel simplement pour raccourcir une note. Les modifications sont appliquées directement dans le dossier hôte monté.
Démarrer l’environnement au démarrage
Lorsque tout fonctionne de manière interactive, le pod devrait se réinitialiser automatiquement après un redémarrage, sans nécessiter de démarrer manuellement Ollama et Hermes. Créez une unité systemd utilisateur :
mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/hermes-local.service
La unité commence par une description et une ligne d’ordre :
[Unit]
Description=Hermes Local AI Pod
After=default.target
La section de service est une opération unique qui lance le pod, l’arrête avec une période de grâce de 30 secondes et laisse deux minutes pour qu’il redémarre :
[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 racine du graphe de Podman se trouve sur un disque séparé, ce disque doit être monté de manière fiable au démarrage, et non être quelque chose que le bureau monte de manière interactive après connexion. Une vérification de montage ExecStartPre optionnelle adaptée au système de fichiers peut être ajoutée.
Recharger systemd :
systemctl --user daemon-reload
Activer l’unité :
systemctl --user enable hermes-local.service
Démarrer :
systemctl --user start hermes-local.service
Et vérifiez :
systemctl --user status hermes-local.service
Puisqu’il s’agit d’un service à usage unique avec RemainAfterExit activé, cet état est normal :
Active: active (exited)
Un mécanisme Quadlet plus récent existe dans Podman pour définir des conteneurs de manière déclarative sous systemd. Cependant, avec un environnement déjà assemblé et testé manuellement, un petit service utilisateur qui lance le pod connu pour fonctionner perturbait le moins.
Pour permettre au service utilisateur sans compte root de démarrer au démarrage, même avant de se connecter au bureau, activez « lingering » :
sudo loginctl enable-linger "$USER"
Vérifiez-le :
loginctl show-user "$USER" -p Linger
Résultat attendu :
Linger=yes
La fonction linger de Systemd permet au gestionnaire de services de l’utilisateur de démarrer au démarrage de l’ordinateur et de rester disponible en dehors des sessions de connexion normales. Après un redémarrage, la liste des conteneurs :
podman ps
Le pod devrait déjà afficher Ollama et Hermes en cours d’exécution. Une session interactive avec Hermes démarre néanmoins avec la commande exec :
podman exec -it \
--user 10000:10000 \
-w /opt/data/workspace \
hermes \
hermes
Le service de démarrage lance l’environnement. Il n’ouvre pas de chat interactif.
Problèmes qui se sont réellement produits
Voici la liste condensée des pannes rencontrées, présentées chacune en tant que problème et sa solution.
Une erreur d’overlay provenant du noyau :
Problem:
kernel does not support overlay fs
Le problème a été résolu en installant fuse-overlayfs et en le définissant comme programme de montage overlay :
Fix:
Install fuse-overlayfs and configure it as the overlay mount program.
Espace insuffisant dans le répertoire temporaire :
Problem:
no space left on device under /var/tmp
Il fallait à la fois un répertoire racine du graphe déplacé le cas échéant et une valeur pour TMPDIR, car le paramètre tmpdir de Podman est une notion distincte :
Fix:
Move Podman's graphroot if needed AND set TMPDIR.
Changing Podman's --tmpdir is not the same thing.
Conflit de nom pour le conteneur bootstrap :
Problem:
ollama-bootstrap name already in use
Celui-ci est résolu en supprimant le conteneur obsolète :
Fix:
podman rm -f ollama-bootstrap
Pasta échoue à ouvrir le dispositif TUN :
Problem:
pasta cannot open /dev/net/tun
Celui-ci est résolu en chargeant le module tun, ou en redémarrant si les modules installés ne correspondent pas au noyau en cours d’exécution :
Fix:
sudo modprobe tun
If the installed modules don't match the running kernel, reboot.
Un outil NVIDIA installé sur une machine AMD :
Problem:
Installed nvidia-container-toolkit on an AMD machine
Cela ne devrait tout simplement pas être fait ; il faut plutôt utiliser l’image ROCm avec les dispositifs kfd et dri :
Fix:
Don't.
Use ollama/ollama:rocm with /dev/kfd and /dev/dri.
Nouveaux membres de groupes de vidéos et de rendus qui n’apparaissaient pas dans la liste des groupes :
Problem:
Added myself to video/render but `groups` still didn't show them
Il est nécessaire de se déconnecter puis de se reconnecter, car la session existante conserve ses groupes originaux :
Fix:
Log out and back in.
The existing login session retains its original supplementary groups.
Fichiers de modèle et manifeste présents tandis que la liste d’Ollama est vide :
Problem:
Ollama model files and manifest exist, but `ollama list` is empty
le diagnostic se fait en consultant le journal ; si les chemins home et models pointent vers la racine du système de fichiers tandis que le volume est monté dans le répertoire de l’utilisateur racine, il convient de définir explicitement les deux 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
Un test en Python de Hermes vers Ollama qui n’a rien affiché en silence :
Problem:
Hermes → Ollama Python test silently prints nothing
Celui-ci a été résolu en ajoutant le drapeau interactif lors de l’utilisation d’un heredoc, ou simplement en utilisant la forme de commande en ligne :
Fix:
If using `python -` with a heredoc, add `podman exec -i`.
Or simply use `python -c`.
Un menu des fournisseurs ne présentant aucune entrée locale Ollama évidente :
Problem:
The Hermes provider menu doesn't contain "local Ollama"
Le problème a été résolu en choisissant l’endpoint personnalisé manuel et en entrant l’URL locale v1 :
Fix:
Choose:
Custom endpoint (enter URL manually)Then:
http://127.0.0.1:11434/v1
Et Hermès se plaignant d’une fenêtre de contexte insuffisante, bien que tout le reste fonctionnât correctement :
Problem:
Everything works, but Hermes reports an inadequate context window
Le problème se résout en définissant la longueur du contexte du côté serveur à 64000, en configurant Hermes avec la même valeur et en vérifiant l’allocation réelle à l’aide de la liste des processus d’Ollama :
Fix:
Set OLLAMA_CONTEXT_LENGTH=64000 server-side and configure Hermes for the same value.
Verify the real allocation with:
ollama ps
Où se trouve la frontière de confiance
La leçon principale ici ne concerne pas vraiment Ollama ou ROCm. Il s’agit plutôt de décider où se situe la frontière de confiance. Une instruction demandant de ne jamais lire de fichiers en dehors d’un répertoire constitue une directive comportementale. Un conteneur Podman auquel on n’a jamais donné accès au reste du système de fichiers hôte représente une frontière de système d’exploitation. Le système de racine sécurisé contre les écritures d’Hermes est utile, mais Hermès le décrit lui-même comme une couche supplémentaire et non comme un véritable sandbox, puisque les commandes de shell peuvent contourner la protection mise en place par les outils de gestion des fichiers.
Le modèle de confiance résultant empile ces couches du plus faible au plus fort, en commençant par les restrictions imposées par les instructions et la protection contre les écritures de fichiers d’Hermes, pour se terminer par la frontière du conteneur :
Prompt restrictions
↓
Hermes file write protections
↓
Hermes container filesystem
↓
Rootless Podman user namespace
↓
Host filesystem permissions
Même si le modèle reçoit une instruction malveillante cachée dans un fichier Markdown, la question importante n’est pas de savoir s’il obéira à l’instruction initiale, mais plutôt ce à quoi l’agent compromis peut réellement accéder. Dans cette conception, la réponse est délibérément limitée.
Débogage par étapes
Hermes et Ollama évoluent rapidement. La documentation actuelle d’Hermes prend en charge des points de terminaison compatibles avec OpenAI hébergés localement, y compris Ollama, mais il existe également des rapports provenant de la communauté concernant le comportement des appels d’outils via des points de terminaison personnalisés d’Ollama. Si la conversation ordinaire fonctionne mais qu’Hermes semble décrire des appels d’outils sans les exécuter, vérifiez le suivi des problèmes actuel d’Hermes avant de supposer que le sandbox Podman est défectueux.
C’est pourquoi il convient de tester l’ensemble du système en tant que chaîne de questions : d’abord pour savoir si Podman détecte la GPU, ensuite si Ollama perçoit le modèle, et enfin si Hermes peut communiquer avec 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?
Ne tentez pas de déboguer les six couches en même temps.
Ce que fournit la configuration finale
La configuration résultante permet l’inférence locale, l’accélération par GPU AMD, une mémoire Hermes persistante et un espace de travail hôte écrivable :
Local inference YES
AMD GPU acceleration YES
Hermes persistent memory YES
One writable host workspace YES
Et il n’a pas besoin de modèle cloud, ne met pas en exposition de système de fichiers hôte, ne met pas en exposition de socket Podman ou Docker, et ne dispose d’aucune sortie Internet normale :
Cloud LLM required NO
Host filesystem exposed NO
Podman/Docker socket exposed NO
Normal Internet egress NO
Entire Obsidian vault exposed NO
C’est une manière bien plus confortable d’expérimenter avec un agent local performant. Hermes dispose encore d’un accès suffisant pour être utile. Il ne peut simplement pas décider par lui-même ce que signifie un accès suffisant.