SSD Nodes Learn Hosting plans →
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-27

Comment auto-héberger openGym avec Docker Compose

Déployez openGym sur un VPS avec Docker Compose : tag Git figé, TLS avant la première passkey, emplacement des données et serveur MCP en lecture seule.

Ce que vous obtenez en auto-hébergeant openGym

Vous auto-hébergez openGym en clonant le dépôt, en modifiant deux lignes dans .env, puis en exécutant docker compose up -d --build derrière un reverse proxy qui termine TLS (transport layer security). openGym est une application de suivi de la pratique sportive et du poids corporel : plans hebdomadaires, entraînements guidés, journalisation de chaque série et suivi du poids au fil du temps. Le logiciel est distribué sous licence AGPL-3.0 et stocke toutes les données dans des fichiers JSON en clair sur votre disque. Vous n’avez donc pas de serveur de base de données à exécuter.

La stack comprend deux conteneurs qui s’exécutent en continu : un conteneur nginx qui sert le build React et un conteneur Node qui héberge l’API. Elle comprend aussi un job exécuté une seule fois, qui télécharge environ 140 MB d’images et de GIF d’exercices lors du premier démarrage.

Le README du projet laisse entendre deux points sans les expliquer à quelqu’un qui déploie l’application sur un serveur public. La connexion par passkey est liée à un hostname. Le domaine et son certificat doivent donc exister avant la première connexion, et non après. Par ailleurs, le serveur MCP facultatif est en lecture seule et s’exécute sur la machine où fonctionne votre client IA, et non à l’intérieur de la stack. Cela modifie les opérations à effectuer lorsque les données se trouvent sur un VPS.

openGym est encore récent. La première release taggée, v1.0.0, est datée du 20 juillet 2026, et v1.2.7 est sortie le 18 août 2026. Treize tags en environ un mois montrent que l’application évolue encore. Récupérez donc un release tag plutôt que de construire ce qui se trouve sur la branche par défaut.

Planifiez le domaine avant la première connexion

Les passkeys servent à vous connecter à openGym. Une passkey est associée à un relying party ID (RP ID), c’est-à-dire au domaine sur lequel l’identifiant a été créé. Les navigateurs ne créent des passkeys qu’en HTTPS. La seule exception est localhost.

Cela entraîne un problème fréquent sur téléphone. Ouvrez http://203.0.113.10:8080 depuis un autre appareil : aucune invite de création de passkey n’apparaît, car le navigateur refuse de créer un identifiant sur une origine HTTP simple ou sur une adresse IP seule. Les notes de dépannage du projet indiquent la même chose : si aucune invite ne s’affiche, vous utilisez http:// ou une adresse IP.

Le problème est plus grave encore : le RP ID est intégré à chaque identifiant déjà enregistré par vos utilisateurs. Si vous modifiez RP_ID par la suite, les passkeys enregistrées sur leurs appareils ne correspondent plus. Personne ne pourra alors se connecter. Choisissez d’abord le nom d’hôte, faites pointer le DNS vers le VPS et configurez le certificat avant que quiconque appuie sur Create profile.

Déployer openGym avec Docker Compose

Le fichier Compose monte ./data et ./media relativement à son propre emplacement. Le répertoire dans lequel vous clonez le projet constitue donc votre base de données. Placez-le à un emplacement persistant.

sudo install -d -o "$USER" -g "$USER" /opt/opengym
git clone https://gitea.com/DuarteSantos/openGym /opt/opengym
cd /opt/opengym
cp .env.example .env

Le README affiche encore une URL de clonage github.com. Cette adresse ne répond plus. Le dépôt Gitea indiqué plus haut est désormais l’emplacement actif du projet.

Modifiez .env. Sur un VPS, trois lignes sont importantes.

RP_ID=gym.example.com
ORIGIN=https://gym.example.com
WEB_PORT=127.0.0.1:8080

RP_ID est le nom d’hôte seul. ORIGIN est l’URL complète, schéma inclus. Ils doivent correspondre exactement à l’adresse affichée dans la barre d’adresse. Sinon, la connexion échoue avec verification failed. La valeur WEB_PORT est expliquée dans la section consacrée au maintien du port 8080 en privé.

docker compose up -d --build
docker compose ps
docker compose logs media

docker compose ps doit afficher web et api comme étant en cours d’exécution, et media comme ayant été arrêté avec le code 0. Cet arrêt est normal : le job media a restart: "no", car son travail consiste en un téléchargement unique. Son journal se termine par une ligne commençant par ✓ Exercise media ready, et ls media/img | wc -l doit afficher quelques centaines, et non 0. Un répertoire vide indique que le téléchargement a échoué. L’application affiche alors des cartes d’exercices dont les images sont vides.

L’option --build est obligatoire ici. Le fichier Compose référence sur ghcr.io des images préconstruites qui ne sont plus publiées. docker compose pull échoue donc avec denied ou manifest unknown, et les deux services sont construits à partir du code source que vous venez de cloner. Tous deux contiennent une section build prévue exactement à cet effet. Si Compose est encore nouveau pour vous, commencez par Docker Compose sur un VPS, puis revenez ici.

Épinglez la version, car ce projet est récent

Comme cet espace de noms du registry n’existe plus, il n’y a plus de tag d’image à épingler. Vous devez donc épingler le checkout présent sur le disque, car il détermine la version de l’application qui sera intégrée au conteneur.

cd /opt/opengym
git fetch --tags
git checkout v1.2.7

git status indique maintenant un HEAD détaché sur ce tag, ce qui convient sur un serveur. Rien ne change tant que vous ne faites pas de checkout vers une autre version.

Demandez ensuite à Compose de ne plus contacter le registry. Placez ceci dans docker-compose.override.yml. Compose charge automatiquement ce fichier et le fusionne avec le fichier suivi. Les clés scalaires sont remplacées par celles du fichier de surcharge. Vous n’avez donc pas besoin de modifier les fichiers dans git, et git pull reste propre. Consultez la façon dont Compose fusionne un fichier de surcharge pour connaître toutes les règles de fusion.

services:
  api:
    pull_policy: build
  web:
    pull_policy: build

Avec cette configuration, un docker compose up -d ultérieur construit l’image à partir des sources présentes au lieu d’échouer lors du pull. Vérifiez que la fusion a bien été appliquée, puis reconstruisez l’image sur ce tag.

docker compose config | grep pull_policy
docker compose up -d --build

Terminer TLS avec un reverse proxy

Les conteneurs utilisent HTTP en clair. Un composant en amont doit gérer le certificat. Caddy est la solution la plus simple, car il demande et renouvelle lui-même le certificat auprès de Let’s Encrypt.

gym.example.com {
    reverse_proxy 127.0.0.1:8080
}

nginx, Traefik et Nginx Proxy Manager fonctionnent de la même manière. Un Cloudflare Tunnel aussi. Le projet le documente et cette solution ne nécessite aucun port entrant ouvert.

curl -sI https://gym.example.com | head -1

Cette commande doit renvoyer HTTP/2 200 sans avertissement de certificat. Ouvrez ensuite le site dans un navigateur et cliquez sur Create profile. Si l’invite de passkey apparaît, puis que la connexion affiche verification failed, RP_ID ou ORIGIN ne correspond pas à l’URL affichée dans la barre d’adresse. Corrigez .env, puis exécutez de nouveau docker compose up -d. Cette commande recrée les conteneurs afin qu’ils lisent les nouvelles valeurs. Un docker compose restart ne recharge pas .env.

Désactiver le port 8080 sur Internet

Par défaut, le service web publie 8080 sur toutes les interfaces. L’application est donc accessible en HTTP non chiffré depuis votre IP publique, tandis que le proxy fournit HTTPS sur le même serveur. Une règle de pare-feu ne corrige pas ce problème. Docker publie un port avec une règle DNAT dans la table nat. Le trafic est ensuite traité dans la chaîne FORWARD, où les règles de Docker l’acceptent. Les règles d’ufw se trouvent sur le chemin INPUT. sudo ufw deny 8080/tcp ne bloque donc rien.

La solution consiste à publier le port uniquement sur l’adresse loopback. Le fichier compose mappe "${WEB_PORT:-8080}:${NGINX_PORT:-80}". La valeur définie dans WEB_PORT est donc substituée à gauche de ce mapping. La syntaxe courte de Docker accepte une paire ip:port à cet emplacement. C’est pourquoi WEB_PORT=127.0.0.1:8080 fonctionne.

docker compose config
sudo ss -ltnp | grep 8080

Dans la configuration fusionnée, sous ports du service web, vous devez voir host_ip: 127.0.0.1. ss doit afficher 127.0.0.1:8080, et non 0.0.0.0:8080. Depuis une autre machine, curl http://<your-vps-ip>:8080 doit maintenant refuser la connexion ou expirer. Le hostname HTTPS doit continuer à fonctionner.

Fermer les inscriptions une fois votre profil créé

Les inscriptions sont ouvertes par défaut et le mode invité est activé. Sur un hostname public, toute personne qui trouve l’URL peut donc créer un profil sur votre serveur. Créez d’abord votre propre profil, puis trouvez votre ID utilisateur : ls data/ affiche un fichier nommé state-<uid>.json pour chaque utilisateur, et ce <uid> correspond à la valeur dont vous avez besoin.

ADMIN_UIDS=<your-uid>
INVITE_ONLY=1
ALLOW_GUEST=0

Relancez docker compose up -d. Settings affiche maintenant un tableau de bord d’administration qui permet de générer et de révoquer des codes d’invitation. Les personnes avec lesquelles vous vous entraînez peuvent ainsi s’inscrire, et personne d’autre. openGym ne connaît pas les identity providers externes. Ces codes d’invitation contrôlent donc uniquement cette application, et rien d’autre sur le serveur. Si vous préférez attribuer un seul compte à chaque personne pour l’ensemble de vos services, placez Authentik devant avec un proxy forward auth afin de contrôler l’accès au hostname avant même que la connexion passkey intégrée d’openGym ne se charge.

Emplacement des données et sauvegarde qui les protège

Tout se trouve dans le répertoire ./data, monté dans le conteneur API sur /data. Il existe quatre types de fichiers : db.json contient les profils et les identifiants passkey publics, state-<uid>.json contient les routines, les entraînements et le poids d’un utilisateur, secret est la clé du cookie de session, et vapid.json contient les clés de notifications push générées au premier démarrage.

cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start api

Arrêtez d’abord l’API, car tar copie les fichiers pendant que l’API peut être en train d’en modifier un. Un fichier JSON copié partiellement est restauré comme un fichier JSON invalide. L’arrêt et le redémarrage prennent environ deux secondes. Copiez ensuite l’archive hors du serveur, car une archive stockée sur le VPS ne survit pas à la perte du VPS. Excluez media/ de la sauvegarde : ce répertoire contient 140 MB d’images d’exercices que le processus de gestion des médias télécharge à nouveau gratuitement.

La restauration consiste à extraire l’archive dans le même chemin sur un hôte qui sert le même domaine. Une passkey stockée sur votre téléphone est associée à l’identifiant RP sur lequel elle a été créée. Une restauration sur un nouveau nom d’hôte vous donne donc une base de données fonctionnelle, mais personne ne peut s’y connecter. Conservez le domaine ou prévoyez de réenregistrer chaque passkey. La même méthode s’applique à tout ce que vous exécutez. Sauvegarder et mettre à niveau une stack Docker Compose présente la procédure générale.

Le serveur MCP est en lecture seule et s’exécute sur votre machine

MCP (model context protocol) permet à un client comme Claude Desktop ou Cursor de communiquer avec un serveur d’outils local. openGym en fournit un dans mcp/. Il ne fait pas partie du fichier compose, ce n’est pas un conteneur et il n’écoute sur aucun port. Le client le démarre comme processus enfant et communique avec lui via stdio. C’est pourquoi le README indique qu’il ne quitte jamais votre machine.

Installez-le là où le client s’exécute, et non sur le serveur :

cd openGym/mcp
npm install

Ajoutez-le ensuite à claude_desktop_config.json :

{
  "mcpServers": {
    "opengym": {
      "command": "node",
      "args": ["/absolute/path/to/openGym/mcp/src/index.js"],
      "env": {
        "OPENGYM_DATA": "/absolute/path/to/openGym/data",
        "OPENGYM_UID": "<your-uid>"
      }
    }
  }
}

OPENGYM_UID est facultatif dans une installation mono-utilisateur, où le serveur détecte l’unique profil trouvé. Il expose huit outils : list_routines, get_routine, get_week_plan, list_workouts, get_workout, get_bodyweight, estimate_1rm et muscle_balance. Ils effectuent tous des lectures. Aucun n’écrit quoi que ce soit. Un assistant peut donc répondre à la question de savoir ce que vous avez entraîné la semaine dernière, mais il ne peut pas enregistrer une série, modifier une routine ni supprimer quoi que ce soit. Cette liste illustre une décision de conception récurrente pour les agents : les outils que vous exposez définissent tout ce qu’un modèle peut faire. Comprendre le fonctionnement des agents en écrivant vous-même la boucle est le moyen le plus rapide de voir pourquoi un ensemble d’outils en lecture seule relève d’un choix de conception et non d’une limitation.

Voici le problème qu’un utilisateur de VPS doit résoudre. OPENGYM_DATA est un chemin de système de fichiers, alors que vos données se trouvent sur le VPS et que votre client IA s’exécute sur votre ordinateur portable. Deux options permettent de gérer cette situation correctement.

  1. Copiez les données sur votre ordinateur et pointez le serveur vers cette copie : rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/, puis définissez OPENGYM_DATA sur ~/opengym-data. Le serveur effectue uniquement des lectures, donc une copie ne fait perdre aucune donnée. Relancez rsync lorsque vous voulez obtenir des valeurs à jour.
  2. Exécutez le serveur via ssh, avec command défini sur ssh et args défini sur ["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"]. Node doit être installé sur le VPS, et la connexion doit n’afficher aucune sortie sur stdout, car stdout transporte le protocole.

Les deux options supposent que l’agent s’exécute lui-même sur votre ordinateur portable. Si vous préférez l’exécuter sur le même serveur que les données, OneCLI fournit à chaque personne un agent isolé sur le serveur, de sorte que le relais stdio vers data/ redevient local.

Si cat data/db.json renvoie Permission denied, le conteneur API a créé ces fichiers avec root et votre compte ne peut pas les lire. Copiez-les avec sudo ou modifiez leur propriétaire sur l’hôte. Pour les serveurs conçus pour écouter sur le réseau plutôt que via stdio, consultez Exécuter des serveurs MCP sur un VPS.

openGym ou wger : lequel déployer ?

wger est l’option établie dans ce domaine, et c’est un logiciel beaucoup plus conséquent. Sa stack Compose exécute gunicorn pour servir une application Django, PostgreSQL, Redis et un worker Celery derrière nginx. En contrepartie, vous bénéficiez du suivi de la nutrition et des ingrédients, d’une API REST documentée, d’une vaste base d’exercices et de fonctions destinées aux coachs qui gèrent les programmes d’autres personnes.

openGym se compose de deux conteneurs, d’un dossier de fichiers JSON et d’aucun compte à administrer en dehors des passkeys. C’est toute la différence. Si vous avez déjà maintenu une installation Chatwoot en fonctionnement, avec une sauvegarde qui consiste en un dump PostgreSQL accompagné du répertoire des fichiers téléversés et chaque mise à niveau qui exécute des migrations de base de données, vous savez déjà ce que la structure de wger implique en matière de maintenance.

Déployez wger si vous voulez suivre votre alimentation en parallèle de votre entraînement, ou si vous avez besoin d’une API sur laquelle vous appuyer. Déployez openGym si vous voulez une stack assez petite pour être lue de bout en bout en un après-midi, avec une connexion sans mot de passe à divulguer. Le coût de ce choix est la maturité : au 19 août 2026, la première release d’openGym date d’un mois, tandis que wger compte plusieurs années de releases. Épinglez votre version, conservez les sauvegardes et lisez les release notes avant chaque mise à jour.

Si vous hésitez encore sur les services qui méritent une place sur le serveur, ce qui mérite d’être auto-hébergé en 2026 présente les compromis à considérer. Cette application trouve facilement sa place aux côtés de Mealie pour les recettes ou d’Actual Budget pour gérer votre argent sur le même petit VPS.

Mise à jour sans rien perdre

cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start api
git fetch --tags

Récupérez la release souhaitée avec git checkout v<new>, puis exécutez docker compose up -d --build pour reconstruire les conteneurs à partir de ce tag. La sauvegarde passe toujours en premier, car la restauration des fichiers JSON sur disque se fait avec une seule commande tar et prend quelques secondes.

FAQ

Pourquoi openGym n’affiche-t-il jamais de demande de passkey sur mon téléphone ?

Le navigateur refuse de créer un credential parce que vous utilisez http:// ou une adresse IP seule, comme http://192.168.1.20:8080. Les navigateurs autorisent les passkeys uniquement sur des origines HTTPS, avec localhost comme seule exception. Placez openGym derrière un reverse proxy utilisant un certificat valide pour un vrai hostname, définissez RP_ID=gym.example.com et ORIGIN=https://gym.example.com dans .env, puis exécutez docker compose up -d pour que les conteneurs prennent en compte les nouvelles valeurs. Si la demande apparaît, mais que la connexion renvoie verification failed, ces deux valeurs ne correspondent pas exactement à l’URL affichée dans la barre d’adresse.

Où openGym stocke-t-il mes données et comment les sauvegarder ?

Dans le répertoire ./data situé à côté du fichier compose et monté dans le conteneur API sous /data. Il contient db.json pour les profils et les credentials de passkey publics, un fichier state-<uid>.json par utilisateur pour les entraînements et le poids, secret pour la clé du cookie de session et vapid.json pour les clés de notification push. Sauvegardez-le avec docker compose stop api, puis tar czf ~/opengym-$(date +%F).tar.gz data/, puis docker compose start api, et copiez l’archive hors du serveur. Excluez media/, qui contient 140 MB d’images d’exercices que le job media retélécharge automatiquement.

Claude peut-il lire mon historique d’entraînement openGym ?

Oui, via le serveur MCP facultatif situé dans le répertoire mcp/, et uniquement en lecture. Il expose huit outils couvrant les routines, les plans hebdomadaires, les entraînements enregistrés, le poids, le nombre maximal estimé sur une répétition et l’équilibre musculaire. Aucun de ces outils n’écrit de données. Il ne s’agit pas d’un conteneur et il n’ouvre aucun port : votre client le démarre via stdio et lit directement les fichiers JSON à l’emplacement OPENGYM_DATA. Comme il s’agit d’un chemin du système de fichiers, exécuter openGym sur un VPS signifie soit synchroniser une copie de data/ vers la machine qui exécute le client, soit invoquer le serveur via ssh dans la configuration du client.

Dois-je auto-héberger openGym ou wger ?

Choisissez wger si vous voulez suivre l’alimentation et la nutrition en plus de vos entraînements, ou disposer d’une API REST documentée sur laquelle vous appuyer. Sa stack est plus importante : Django sous gunicorn, PostgreSQL, Redis et un worker Celery derrière nginx. Choisissez openGym si vous voulez deux conteneurs, des fichiers JSON lisibles avec cat et une connexion par passkey sans mot de passe à gérer. Au 19 août 2026, la première release taguée d’openGym date d’un mois seulement. Consultez donc un tag git et sauvegardez data/ avant chaque mise à jour.