me
Back to all articles
Post in FrenchFrench flag
Daryl Ngako

Installer Hermes Agent sur un VPS avec Docker : le guide complet

Comment j'ai installé Hermes Agent (l'OpenClaw Killer) sur un VPS : connexion SSH, OAuth, configuration DNS, reverse proxy HTTPS et les pièges réels de prod.

DockerIATutoriel
Installer Hermes Agent sur un VPS avec Docker : le guide complet

Qu'est-ce qu'un agent IA autonome ? C'est un programme à qui tu donnes un objectif, pas une suite d'instructions. Il décide lui-même des étapes, utilise des outils (web, fichiers, terminal, code) et avance seul jusqu'au résultat. Là où un chatbot répond, un agent agit.

Hermes, c'est exactement ça sauf que tu peux le faire tourner sur ta propre machine, que tu contrôles de bout en bout. Et c'est là que ça se corse : connexion SSH, authentification qui expire toutes les heures, DNS, permissions de fichiers... autant d'embûches que les tutoriels passent sous silence.

Dans ce guide, je te montre comment j'ai déployé Hermes Agent (de Nous Research) sur un VPS Ubuntu avec Docker, de la première connexion SSH jusqu'à l'agent qui répond dans une interface de chat accessible via ton nom de domaine. Tu vas apprendre à te connecter au serveur, à conteneuriser Hermes, à le brancher sur un fournisseur d'inférence via OAuth, à configurer tes DNS et à l'exposer derrière un reverse proxy HTTPS.

C'est un retour d'expérience terrain, pas une doc théorique. Chaque piège que je décris, je l'ai rencontré et résolu pour de vrai.

C'est quoi Hermes Agent ?

Hermes Agent, c'est un agent IA autonome et open source développé par Nous Research, qui apprend, mémorise et s'améliore automatiquement de session à session. Là où un chatbot classique se contente de répondre, Hermes peut chercher sur le web, lire et écrire des fichiers, exécuter du code dans un terminal, générer des images, et bien plus.

Imagine la différence entre un collègue qui te donne des conseils par téléphone et un collègue qui s'assoit à ton poste et fait le travail. Le premier, c'est un LLM brut. Le second, c'est un agent.

Concrètement, Hermes se branche sur un fournisseur d'inférence (ici Nous Portal, qui donne accès à plus de 300 modèles comme Claude Sonnet, GPT, etc.) et y ajoute toute une couche d'outils orchestrés. Le projet est développé par Nous Research, et sa documentation officielle se trouve sur hermes-agent.nousresearch.com.

Site de Hermes Agent

Hermes Agent vs OpenClaw

L'autre grand nom du créneau, c'est OpenClaw (ex-Moltbot) : un agent open source qui vit dans tes messageries (WhatsApp, Telegram, Slack…), avec un système de skills communautaires et un heartbeat qui le fait agir tout seul.

J'ai choisi Hermes pour trois raisons :

  • Intégration : Hermes expose une API compatible OpenAI, donc il se branche sur ma stack (Open WebUI, scripts). OpenClaw, lui, est taillé pour la messagerie.

  • Intelligence différente : Il utilise beaucoup Python (plutôt que Node.js), ce qui lui donne une couche d'intelligence différente et parfois plus adaptée.

  • *Auto-amélioration via les skills : Hermes peut créer, modifier et apprendre de nouveaux "skills" au fil de l'utilisation (Gmail, Telegram, etc.). Il comprend et modifie même son propre code.

  • *Système de mémoire structuré : Il possède des fichiers memory.md et user.md avec une gestion de contexte propre (context files, @références), ce qui lui permet de mieux se souvenir des préférences et habitudes de l'utilisateur.

Site de Hermes Agent

Pourquoi le conteneuriser sur un VPS ?

Faire tourner Hermes en local sur ta machine, c'est bien pour tester. Mais pour un usage sérieux, tu veux qu'il tourne en permanence, qu'il survive aux redémarrages, et qu'il soit accessible de partout. C'est exactement le rôle d'un VPS + Docker.

Voici les bénéfices concrets de cette approche :

  • Disponibilité 24/7 : ton agent tourne même quand ton laptop est fermé.
  • Isolation : Docker encapsule Hermes et ses dépendances, sans polluer le système hôte.
  • Reproductibilité : ton Dockerfile décrit l'environnement exact, tu peux le redéployer ailleurs en une commande.
  • Accessibilité : derrière un nom de domaine et un reverse proxy, tu accèdes à ton agent depuis n'importe quel navigateur.

Étape 1 : Se connecter au VPS en SSH

Avant de toucher à Docker, il faut accéder à ton serveur. La connexion se fait en SSH (Secure Shell), le protocole standard pour piloter une machine distante en ligne de commande.

Ton hébergeur t'a fourni trois informations : l'adresse IP du VPS, un nom d'utilisateur (souvent ubuntu ou root), et soit un mot de passe, soit une clé SSH. Je te recommande fortement la clé SSH, bien plus sûre qu'un mot de passe.

Pour te connecter avec une clé privée :

ssh -i /chemin/vers/ta_cle_privee ubuntu@ton.adresse.ip

Sur Windows, le chemin ressemble à C:\Users\TonNom\.ssh\private_key. Sur Mac ou Linux, plutôt ~/.ssh/id_rsa.

Si c'est ta première connexion, SSH te demande de confirmer l'empreinte du serveur, tape yes. Une fois connecté, ton prompt change pour afficher le hostname du serveur, par exemple :

ubuntu@mon-vps:~$

Le $ à la fin du prompt indique que tu es un utilisateur normal. Un # indiquerait que tu es root. Travaille autant que possible en utilisateur normal (ubuntu) et n'utilise sudo que quand c'est nécessaire.

Vérifie que Docker est bien installé sur le VPS :

docker --version
docker compose version

Si Docker n'est pas là, installe-le via le script officiel :

curl -fsSL https://get.docker.com | sh

Maintenant que tu es connecté et que Docker tourne, on peut construire notre image.

Étape 2 : Préparer le Dockerfile

L'idée est de partir d'une image Python légère et d'installer Hermes via pipx (qui isole l'outil dans son propre environnement). On ajoute aussi le binaire Docker statique, indispensable si tu veux que ton agent puisse créer des conteneurs sandbox plus tard.

Voici le Dockerfile que j'utilise :

Dockerfile
FROM python:3.12-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
        curl nodejs npm && \
    rm -rf /var/lib/apt/lists/*

# CLI docker (binaire statique) pour le backend terminal sandboxé
RUN curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \
    | tar -xz -C /usr/local/bin --strip-components=1 docker/docker

RUN pip install --no-cache-dir pipx && \
    pipx install hermes-agent && \
    pipx inject hermes-agent aiohttp fastapi uvicorn ptyprocess "mcp>=1.27" httpx-sse langfuse && \
    ln -s /root/.local/bin/hermes /usr/local/bin/hermes

WORKDIR /root

COPY start.sh /start.sh
RUN chmod +x /start.sh

EXPOSE 8642 9119
CMD ["/start.sh"]

Quelques points clés sur ce fichier :

  • pipx inject ajoute des dépendances supplémentaires dans l'environnement de Hermes. Sans aiohttp, fastapi et uvicorn, le serveur API ne démarre pas. C'est l'une des erreurs les plus fréquentes au premier lancement.
  • Le binaire Docker statique est téléchargé directement depuis le dépôt officiel Docker. Il te servira si tu veux que l'agent exécute du code dans des conteneurs isolés.
  • EXPOSE 8642 9119 déclare deux ports : le 8642 pour le serveur API (compatible OpenAI), le 9119 pour le dashboard web.

Étape 3 : Le script de démarrage

Le CMD du Dockerfile pointe vers un start.sh. Ce script lance le dashboard en arrière-plan, puis la gateway (le serveur API) au premier plan :

start.sh
#!/bin/sh
umask 0000
# Dashboard en arrière-plan
hermes dashboard --host 0.0.0.0 --port 9119 --no-open --insecure --skip-build &
# Gateway au premier plan (inclut le serveur API)
exec hermes gateway

Le umask 0000 mérite une explication. Par défaut, Hermes (qui tourne en root dans le conteneur) crée ses fichiers en 0600, donc lisibles uniquement par root. Si tu partages un dossier avec d'autres outils, ils ne pourront pas lire ces fichiers. Le umask 0000 force la création en mode permissif.

Ca t'évitera des heures de debug sur des "Permission denied" mystérieux.

Étape 4 : Le docker-compose

On assemble tout dans un docker-compose.yml. Ici, je connecte Hermes à un réseau Docker existant (celui où tournent mes autres services) et je monte le volume de la config Hermes.

docker-compose.yml
services:
  hermes-api:
    build: .
    container_name: hermes-api
    restart: unless-stopped
    expose:
      - "8642"
      - "9119"
    volumes:
      - ./auth:/root/.hermes
    networks:
      - n8nnet

networks:
  n8nnet:
    external: true
    name: mon-reseau-docker

Le montage ./auth:/root/.hermes est crucial : il fait persister la configuration et l'authentification de Hermes hors du conteneur. Sans ça, à chaque redémarrage tu perdrais ta connexion au fournisseur d'inférence.

Règle à retenir : une modification du docker-compose.yml (ajout d'un volume, d'un port) nécessite de recréer le conteneur avec docker compose up -d --force-recreate. Un simple restart ne suffit pas à appliquer un nouveau volume.

Étape 5 : Connecter Hermes au fournisseur d'inférence

C'est là que ça devient intéressant. Hermes a besoin d'un cerveau, c'est-à-dire d'un modèle d'inférence. Dans mon cas, je passe par Nous Portal en OAuth.

Lance la commande d'authentification dans le conteneur :

docker exec -it hermes-api hermes auth add nous --type oauth --no-browser

Authentification pour Hermes dans le conteneur

Le flag --no-browser est indispensable sur un serveur headless : il te donne une URL à ouvrir sur ta propre machine, tu t'authentifies, puis tu colles le code de retour.

Une fois connecté, sélectionne ton modèle par défaut :

docker exec -it hermes-api hermes model

Choix du modèle de l'agent

Vérifie que tout est en place :

docker exec hermes-api hermes portal info

Tu dois voir ✓ logged in et la liste des outils disponibles (web search, image generation, etc.).

Vérification de la connexion à Hermes Agent

Où vit l'authentification : le fichier auth.json

Quand tu te connectes en OAuth, Hermes stocke tes identifiants dans un fichier auth.json, à l'intérieur de /root/.hermes/. Ce fichier contient deux choses importantes : un access token (le jeton qui sert à appeler l'API) et un refresh token (le jeton qui sert à régénérer l'access token quand il expire).

L'access token OAuth a une durée de vie courte, quelques heures en général. Passé ce délai, il devient invalide. Pour ne pas avoir à te reconnecter manuellement toutes les heures, Hermes utilise le refresh token pour réécrire automatiquement un nouveau access token dans auth.json. C'est ce mécanisme de refresh qui permet à ton agent de tourner en continu sans intervention.

Le piège qui casse le refresh (et comment je l'ai résolu)

Premier vrai obstacle que j'ai rencontré : après quelques heures, l'agent tombait en erreur d'authentification, et le refresh automatique échouait avec un message du type "Device or resource busy".

La cause était subtile. Dans mon premier compose, j'avais monté le fichier auth.json seul comme volume Docker, façon ./auth.json:/root/.hermes/auth.json. Le problème : quand Docker monte un fichier individuel, il le verrouille en place. Or le mécanisme de refresh de Hermes ne se contente pas de modifier le contenu du fichier, il fait une réécriture atomique (il écrit un fichier temporaire puis remplace l'ancien par un rename). Et tu ne peux pas remplacer un fichier que Docker a monté individuellement. D'où le "Device busy", et le refresh qui échoue en boucle.

La solution est de monter le dossier parent complet, pas le fichier :

volumes:
  - ./auth:/root/.hermes      # ✅ le dossier entier
  # PAS ./auth/auth.json:/root/.hermes/auth.json  ❌ le fichier seul

En montant le dossier /root/.hermes en entier, Hermes peut créer son fichier temporaire et faire le rename à l'intérieur du dossier monté, sans conflit avec Docker. Le refresh fonctionne alors parfaitement, et ton token se régénère tout seul indéfiniment.

Règle à retenir : avec Docker, monte toujours le dossier de config, jamais un fichier de config isolé qui doit être réécrit par l'application. C'est valable pour Hermes, mais aussi pour plein d'autres outils qui font de la réécriture atomique.

Si tu t'es déjà retrouvé dans l'état cassé (fichier monté seul), corrige le compose puis force une reconnexion propre :

docker exec -it hermes-api hermes auth add nous --type oauth --no-browser

Le nouveau login réécrit un auth.json valide dans le dossier correctement monté.

Étape 6 : Exposer l'agent avec un reverse proxy et HTTPS

Ton agent tourne, mais il n'est accessible que depuis l'intérieur du réseau Docker. Pour y accéder via un nom de domaine avec HTTPS, deux choses à mettre en place : faire pointer ton domaine vers ton VPS (les DNS), puis router le trafic vers le bon conteneur (le reverse proxy).

Configurer les DNS de ton nom de domaine

Avant qu'un nom comme dash.mondomaine.com mène à ton serveur, il faut créer un enregistrement DNS qui associe ce nom à l'adresse IP de ton VPS. C'est ce qui permet au navigateur de savoir où aller quand quelqu'un tape ton domaine.

Rends-toi dans l'interface de gestion DNS de ton registrar (l'endroit où tu as acheté ton domaine, type OVH, Cloudflare, Namecheap, Infomaniak) et crée un enregistrement de type A :

TypeNom (sous-domaine)Valeur (cible)
Adashton.adresse.ip.vps
Achatton.adresse.ip.vps

Le type A associe un nom à une adresse IPv4. Le "Nom" est le sous-domaine que tu veux (dash donnera dash.mondomaine.com), et la "Valeur" est l'IP de ton VPS, la même que celle que tu utilises pour te connecter en SSH.

Si tu veux exposer plusieurs services (dashboard, interface de chat, etc.), crée un enregistrement A par sous-domaine. Astuce alternative : un enregistrement wildcard * qui fait pointer tous les sous-domaines vers ton IP d'un coup, pratique si tu prévois d'en ajouter beaucoup.

Attention : la propagation DNS n'est pas instantanée. Compte de quelques minutes à quelques heures selon ton registrar.

Tu peux vérifier que ton enregistrement est actif avec la commande dig depuis ton VPS :

dig +short dash.mondomaine.com

Si ça renvoie l'IP de ton VPS, le DNS est propagé et tu peux passer à la suite. Si ça ne renvoie rien, patiente, l'enregistrement n'est pas encore propagé.

Mettre en place le reverse proxy Caddy

Une fois les DNS en place, j'utilise Caddy comme reverse proxy. Son gros avantage : il obtient et renouvelle automatiquement les certificats HTTPS Let's Encrypt, sans configuration manuelle. Dès que ton DNS pointe correctement, Caddy détecte le domaine et génère le certificat tout seul.

Voici un bloc Caddy type pour exposer le dashboard, protégé par une authentification basique :

Caddyfile
dash.mondomaine.com {
    basic_auth {
        admin <hash_bcrypt_du_mot_de_passe>
    }
    reverse_proxy hermes-api:9119
}

Pour générer le hash bcrypt de ton mot de passe :

docker exec caddy caddy hash-password --plaintext "ton-mot-de-passe"

Après avoir modifié le Caddyfile, recharge la config sans couper le service :

docker exec caddy caddy reload --config /etc/caddy/Caddyfile

Le cas des interfaces WebSocket

Si tu exposes une interface qui utilise des WebSockets (comme une instance Obsidian web ou certaines UI temps réel), le simple reverse_proxy peut ne pas suffire. Ajoute les en-têtes qui préservent l'origine :

Caddyfile
obsidian.mondomaine.com {
    basic_auth {
        admin <hash_bcrypt>
    }
    reverse_proxy obsidian:3000 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
    }
}

J'ai galéré sur ce point : la page se chargeait à moitié puis affichait "Erreur de chargement". Le certificat HTTPS était bien obtenu, le conteneur répondait, mais le handshake WebSocket échouait. Les header_up ont réglé le problème.

Étape 7 : Connecter une interface de chat

Pour discuter avec ton agent dans un vrai chat (plutôt qu'en ligne de commande), Open WebUI est parfait. Il consomme l'API compatible OpenAI que Hermes expose.

Dans Open WebUI, va dans Admin Settings → Connections → Add Connection et renseigne :

ChampValeur
URLhttp://hermes-api:8642/v1
KeyTa clé API Hermes (dans /root/.hermes/.env)

Ajout de la connexion dans Open WebUI

Ton agent apparaît alors dans le menu déroulant des modèles. Sélectionne-le, et tu peux lui parler. La différence avec un modèle brut ? Quand tu lui demandes de lire un fichier ou de chercher sur le web, il utilise réellement ses outils.

Interface Open WebUI

Attention : ne confonds pas l'agent (qui a les outils) avec un modèle brut connecté en parallèle. Si tu sélectionnes un modèle brut dans le menu, il te répondra "je n'ai pas accès aux fichiers". Vérifie toujours que c'est bien ton agent Hermes qui est sélectionné.

Les pièges réels que tu vas rencontrer

Voici les obstacles que j'ai croisés en production, avec leur solution. C'est la partie que les tutoriels oublient toujours.

Le disque qui sature

Docker stocke ses images et conteneurs dans /var/lib/docker, sur le disque système. Si ton VPS a un petit disque système (le mien faisait moins de 20 Go), tu satures vite, surtout quand Hermes crée des conteneurs sandbox.

La solution : déplacer le stockage Docker sur ton volume de données. Crée ou édite /etc/docker/daemon.json :

/etc/docker/daemon.json
{
  "data-root": "/mnt/data/docker"
}

Pour containerd, ajuste aussi /etc/containerd/config.toml pour pointer root vers /mnt/data/containerd. Après un redémarrage du démon, ton disque système respire.

Les permissions sur les fichiers partagés

Hermes tourne en root, mais d'autres outils (Obsidian, ton utilisateur SSH) tournent souvent en UID 1000. Quand Hermes écrit dans un dossier partagé, les fichiers appartiennent à root et sont illisibles par les autres.

Le correctif rapide :

sudo chmod -R 777 ~/hermes-proxy/vault
sudo chown -R 1000:1000 ~/hermes-proxy/vault

Le correctif permanent, c'est le umask 0000 dans le start.sh qu'on a vu plus haut. Il fait que les nouveaux fichiers sont créés en mode lisible par tous dès le départ.

Les commandes Hermes passent toujours par le conteneur

Petit rappel qui m'a fait perdre du temps : si tu as installé Hermes uniquement dans le conteneur (pour économiser de l'espace), la commande hermes n'existe pas sur l'hôte. Tu obtiens un command not found. Toute commande doit passer par :

docker exec -it hermes-api hermes [commande]

Toute la config de Hermes Agent

Récapitulatif

Voici les étapes essentielles pour déployer Hermes sur ton VPS :

ÉtapeCommande clé
Se connecter au VPSssh -i cle_privee ubuntu@ton.ip
Construire l'imagedocker compose up -d --build
Authentifier le fournisseurhermes auth add nous --type oauth --no-browser
Choisir le modèlehermes model
Vérifier l'étathermes portal info
Vérifier un DNSdig +short dash.mondomaine.com
Recréer après modif composedocker compose up -d --force-recreate

Et les pièges à garder en tête :

  • Monte le dossier .hermes, jamais le fichier auth.json isolé (sinon le refresh OAuth casse).
  • Ajoute umask 0000 pour éviter les soucis de permissions.
  • Attends la propagation DNS avant que Caddy puisse générer le certificat HTTPS.
  • Déplace le stockage Docker sur un gros volume si ton disque système est petit.
  • Pour les UI WebSocket derrière Caddy, ajoute les header_up.

Tu as maintenant un agent IA autonome qui tourne sur ton propre serveur, accessible en SSH pour l'admin et via une interface de chat sur ton domaine, avec accès à des outils. C'est la fondation solide.

Mais un seul agent, aussi capable soit-il, reste limité : il fait tout lui-même, en série. La vraie montée en puissance, c'est quand tu passes d'un agent unique à une organisation d'agents qui collaborent. Imagine un orchestrateur qui reçoit un objectif, le découpe en tâches, et les distribue à des dizaines de spécialistes (un agent recherche, un agent rédaction, un agent qui vérifie le travail des autres), le tout coordonné par un tableau kanban partagé.

C'est exactement ce que je te montre dans le prochain article : comment construire une armée de 48 agents IA sur Hermes, avec un système d'orchestration, de délégation via kanban, et des tâches planifiées en autonomie par cron. Tu y découvriras le système de profils, le rôle du fichier SOUL.md, et les pièges costauds que j'ai dû résoudre (la limite du superviseur s6, les conflits de bot, l'environnement qui casse la délégation).

Si ce guide t'a permis de poser la fondation, le prochain te montre comment bâtir l'immeuble dessus.