openGym selbst hosten: Docker-Setup für Ihren VPS
So hosten Sie openGym mit Docker Compose: Git-Tag festlegen, TLS vor dem ersten Passkey einrichten, JSON-Daten finden und den schreibgeschützten MCP-Server nutzen.
Was Sie beim Self-Hosting von openGym erhalten
Sie hosten openGym selbst, indem Sie das Repository klonen, zwei Zeilen in .env bearbeiten und docker compose up -d --build hinter einem Reverse Proxy ausführen, der TLS (Transport Layer Security) terminiert. openGym ist ein Tracker für Fitness- und Körpergewichtsübungen: Wochenpläne, angeleitete Workouts, Protokollierung jedes Satzes und Gewichtsentwicklung über die Zeit. Die Software steht unter der AGPL-3.0-Lizenz und speichert alle Daten als einfache JSON-Dateien auf Ihrer Festplatte. Sie müssen daher keinen Datenbankserver betreiben.
Der Stack besteht aus zwei dauerhaft laufenden Containern: einem nginx-Container, der das React-Build bereitstellt, und einem Node-Container, der die API enthält. Zusätzlich gibt es einen einmalig ausgeführten Job, der beim ersten Start etwa 140 MB an Übungsbildern und GIFs herunterlädt.
Zwei Punkte deutet die README des Projekts an, führt sie für den Betrieb auf einem öffentlichen Server aber nicht ausdrücklich aus. Die Passkey-Anmeldung ist an einen Hostnamen gebunden. Daher müssen die Domain und ihr Zertifikat vor der ersten Anmeldung vorhanden sein, nicht erst danach. Der optionale MCP-Server arbeitet nur lesend und läuft auf dem Rechner, auf dem Ihr AI-Client ausgeführt wird, nicht innerhalb des Stacks. Das ändert die erforderlichen Schritte, wenn die Daten auf einem VPS liegen.
openGym ist noch jung. Das erste getaggte Release, v1.0.0, ist auf den 20. Juli 2026 datiert. v1.2.7 wurde am 18. August 2026 veröffentlicht. Dreizehn Tags in etwa einem Monat zeigen, dass die Anwendung noch aktiv weiterentwickelt wird. Verwenden Sie daher ein Release-Tag, statt den aktuellen Stand des Default-Branches zu bauen.
Den Domainnamen vor der ersten Anmeldung festlegen
Für die Anmeldung bei openGym verwenden Sie Passkeys. Ein Passkey ist an eine Relying-Party-ID (RP ID) gebunden. Dabei handelt es sich um die Domain, unter der das Anmeldemittel erstellt wurde. Browser erstellen Passkeys nur über HTTPS. Die einzige Ausnahme ist localhost.
Das führt zu einem Problem, das Nutzer auf ihrem Smartphone bemerken. Öffnen Sie http://203.0.113.10:8080 auf einem anderen Gerät, erscheint überhaupt keine Aufforderung zur Erstellung eines Passkeys. Der Browser verweigert die Erstellung eines Anmeldemittels über eine reine HTTP-Quelle oder eine nicht aufgelöste IP-Adresse. Auch die Hinweise zur Fehlerbehebung des Projekts nennen genau dieses Verhalten: Wenn keine Aufforderung erscheint, verwenden Sie http:// oder eine IP-Adresse.
Noch problematischer ist, dass die RP ID in jedem bereits registrierten Anmeldemittel Ihrer Nutzer gespeichert ist. Ändern Sie RP_ID später, stimmen die auf den Geräten gespeicherten Passkeys nicht mehr überein, sodass sich niemand mehr anmelden kann. Legen Sie den Hostnamen daher zuerst fest, verweisen Sie DNS auf den VPS und richten Sie das Zertifikat ein, bevor jemand auf Create profile tippt.
openGym mit Docker Compose bereitstellen
Die Compose-Datei bind-mountet ./data und ./media relativ zu sich selbst. Das Verzeichnis, in das Sie das Repository klonen, ist daher zugleich Ihre Datenbank. Wählen Sie dafür einen dauerhaften Speicherort.
sudo install -d -o "$USER" -g "$USER" /opt/opengym
git clone https://gitea.com/DuarteSantos/openGym /opt/opengym
cd /opt/opengym
cp .env.example .envDie README-Datei enthält weiterhin eine github.com-Klon-URL. Diese Adresse ist nicht mehr erreichbar. Das oben genannte Gitea-Repository ist der aktuelle Speicherort des Projekts.
Bearbeiten Sie .env. Auf einem VPS sind drei Zeilen relevant.
RP_ID=gym.example.com
ORIGIN=https://gym.example.com
WEB_PORT=127.0.0.1:8080RP_ID ist der reine Hostname. ORIGIN ist die vollständige URL einschließlich Schema. Beide müssen exakt der Adresse in der Adressleiste entsprechen. Andernfalls schlägt die Anmeldung mit verification failed fehl. Der Wert WEB_PORT wird im Abschnitt zum privaten Halten von Port 8080 erläutert.
docker compose up -d --build
docker compose ps
docker compose logs mediadocker compose ps sollte web und api als laufend anzeigen. media sollte mit Exit-Code 0 beendet sein. Das ist korrekt: Der Medienjob hat restart: "no", weil seine Aufgabe aus einem einmaligen Download besteht. Sein Log endet mit einer Zeile, die mit ✓ Exercise media ready beginnt. ls media/img | wc -l sollte einige hundert Dateien statt 0 ausgeben. Ein leeres Verzeichnis bedeutet, dass der Download fehlgeschlagen ist. Die Anwendung rendert dann Übungskarten mit leeren Bildern.
Das Flag --build ist hier erforderlich. Die Compose-Datei nennt vorgefertigte Images auf ghcr.io, die nicht mehr veröffentlicht werden. Daher schlägt docker compose pull mit denied oder manifest unknown fehl. Die beiden Dienste werden stattdessen aus dem gerade geklonten Quellcode erstellt. Beide enthalten dafür einen Abschnitt build. Wenn Compose für Sie neu ist, beginnen Sie mit Docker Compose auf einem VPS und kehren Sie anschließend hierher zurück.
Version festsetzen, weil dieses Projekt noch jung ist
Da dieser Registry-Namespace nicht mehr existiert, gibt es kein Image-Tag mehr, das Sie festsetzen können. Stattdessen legen Sie den Checkout auf dem Datenträger fest. Er bestimmt, welche Version der Anwendung im Container landet.
cd /opt/opengym
git fetch --tags
git checkout v1.2.7git status meldet jetzt einen detached HEAD an diesem Tag. Das ist auf einem Server der gewünschte Zustand. Unter diesem Checkout ändert sich nichts, bis Sie einen anderen auschecken.
Weisen Sie Compose anschließend an, die Registry überhaupt nicht mehr abzufragen. Schreiben Sie dies in docker-compose.override.yml. Compose lädt diese Datei automatisch und führt sie mit der versionierten Datei zusammen. Skalare Schlüssel werden durch das Override ersetzt. Daher muss nichts in git geändert werden, und git pull bleibt unverändert. Unter wie Compose eine Override-Datei zusammenführt finden Sie die vollständigen Regeln für die Zusammenführung.
services:
api:
pull_policy: build
web:
pull_policy: buildDanach erstellt ein späteres docker compose up -d das Image aus dem vorhandenen Quellcode, statt bei einem Pull-Vorgang fehlzuschlagen. Prüfen Sie, ob die Zusammenführung wirksam ist, und erstellen Sie das Image anschließend am festgelegten Tag neu.
docker compose config | grep pull_policy
docker compose up -d --buildTLS-Terminierung mit einem Reverse Proxy
Die Container verwenden unverschlüsseltes HTTP. Davor muss ein Dienst das Zertifikat verwalten. Caddy ist der kürzeste Weg, weil es das Zertifikat selbst bei Let's Encrypt anfordert und erneuert.
gym.example.com {
reverse_proxy 127.0.0.1:8080
}nginx, Traefik und Nginx Proxy Manager funktionieren auf dieselbe Weise. Das gilt auch für einen Cloudflare Tunnel, den das Projekt dokumentiert und der überhaupt keinen eingehenden Port benötigt.
curl -sI https://gym.example.com | head -1Das sollte HTTP/2 200 ohne Zertifikatswarnung zurückgeben. Öffnen Sie die Website nun in einem Browser und wählen Sie Create profile. Wenn die Passkey-Abfrage erscheint und die Anmeldung anschließend meldet, stimmt verification failed, RP_ID oder ORIGIN nicht mit der URL in der Adressleiste überein. Korrigieren Sie .env und führen Sie docker compose up -d erneut aus. Dadurch werden die Container neu erstellt, sodass sie die neuen Werte einlesen. Ein docker compose restart lädt .env nicht neu.
Port 8080 vom öffentlichen Internet fernhalten
Standardmäßig veröffentlicht der Webdienst 8080 auf allen Schnittstellen. Dadurch ist die Anwendung über die öffentliche IP-Adresse per HTTP erreichbar, während der Proxy auf demselben Server HTTPS bereitstellt. Eine Firewall-Regel behebt das nicht. Docker veröffentlicht einen Port mit einer DNAT-Regel in der Tabelle nat. Dieser Datenverkehr wird anschließend in der Kette FORWARD verarbeitet, in der Dockers eigene Regeln ihn akzeptieren. Die Regeln von ufw greifen dagegen auf dem Pfad INPUT. sudo ufw deny 8080/tcp blockiert daher nichts.
Die Lösung besteht darin, den Port nur an die Loopback-Adresse zu binden. Die Compose-Datei verwendet die Zuordnung "${WEB_PORT:-8080}:${NGINX_PORT:-80}". Der in WEB_PORT gesetzte Wert wird daher auf der linken Seite dieser Zuordnung eingesetzt. Die Kurzsyntax von Docker akzeptiert dort ein ip:port-Paar. Deshalb funktioniert WEB_PORT=127.0.0.1:8080.
docker compose config
sudo ss -ltnp | grep 8080In der zusammengeführten Konfiguration sollte unter ports des Webdienstes host_ip: 127.0.0.1 angezeigt werden. ss sollte 127.0.0.1:8080 anzeigen und nicht 0.0.0.0:8080. Von einem anderen Rechner aus sollte curl http://<your-vps-ip>:8080 jetzt abgewiesen werden oder in einen Timeout laufen. Der HTTPS-Hostname sollte weiterhin funktionieren.
Anmeldung schließen, sobald Ihr Profil vorhanden ist
Die Anmeldung ist standardmäßig geöffnet, und der Gastmodus ist aktiviert. Bei einem öffentlich erreichbaren Hostnamen bedeutet das, dass jeder, der die URL findet, ein Profil auf Ihrem Server anlegen kann. Registrieren Sie zuerst Ihr eigenes Profil und ermitteln Sie anschließend Ihre Benutzer-ID: ls data/ listet für jeden Benutzer eine Datei namens state-<uid>.json auf, und <uid> enthält den benötigten Wert.
ADMIN_UIDS=<your-uid>
INVITE_ONLY=1
ALLOW_GUEST=0Führen Sie docker compose up -d erneut aus. Unter Settings wird jetzt ein Admin-Dashboard angezeigt. Dort können Sie Einladungscodes erstellen und widerrufen, sodass sich die Personen, mit denen Sie trainieren, registrieren können und sonst niemand. openGym kennt keine externen Identity Provider. Daher gelten diese Einladungscodes nur für diese eine Anwendung und für nichts anderes auf dem Server. Wenn Sie stattdessen für jede Person über alle von Ihnen betriebenen Dienste hinweg ein einzelnes Konto bereitstellen möchten, setzen Sie Authentik als Forward-Auth-Proxy davor. Dadurch wird der Hostname geschützt, bevor die eigene Passkey-Anmeldung von openGym überhaupt geladen wird.
Wo die Daten liegen und welches Backup sie schützt
Alles befindet sich im Verzeichnis ./data, das im API-Container unter /data eingebunden ist. Es gibt vier Dateitypen: db.json enthält Profile und öffentliche Passkey-Anmeldedaten, state-<uid>.json enthält die Routinen, Workouts und das Körpergewicht eines Benutzers, secret ist der Schlüssel für das Session-Cookie und vapid.json enthält die beim ersten Start generierten Schlüssel für Push-Benachrichtigungen.
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start apiStoppen Sie die API zuerst, weil tar Dateien kopiert, während die API möglicherweise gerade eine Datei schreibt. Eine teilweise kopierte JSON-Datei wird als beschädigte JSON-Datei wiederhergestellt. Das Stoppen und Starten dauert etwa zwei Sekunden. Kopieren Sie das Archiv anschließend vom Server herunter, weil ein auf dem VPS liegendes Archiv den Ausfall des VPS nicht übersteht. Lassen Sie media/ beim Backup weg: Es handelt sich um 140 MB Übungsbilder, die der Media-Job kostenlos erneut herunterlädt.
Für die Wiederherstellung muss das Archiv auf einem Host, der dieselbe Domain bereitstellt, in denselben Pfad entpackt werden. Ein auf dem Telefon gespeicherter Passkey ist an die RP ID gebunden, unter der er erstellt wurde. Bei der Wiederherstellung unter einem neuen Hostnamen erhalten Sie daher zwar eine funktionierende Datenbank, aber niemand kann sich anmelden. Behalten Sie die Domain bei oder planen Sie, jeden Passkey neu zu registrieren. Die gleiche Vorgehensweise gilt für alles andere, was Sie betreiben. Das Backup und Upgrade eines Docker-Compose-Stacks beschreibt den allgemeinen Ablauf.
Der MCP-Server ist schreibgeschützt und läuft auf Ihrem Computer
MCP (Model Context Protocol) ermöglicht die Kommunikation eines Clients wie Claude Desktop oder Cursor mit einem lokalen Tool-Server. openGym liefert einen solchen Server in mcp/ aus. Er ist nicht Bestandteil der Compose-Datei, kein Container und lauscht an keinem Port. Der Client startet ihn als untergeordneten Prozess und kommuniziert über stdio mit ihm. Deshalb steht in der README, dass er Ihre Maschine nie verlässt.
Installieren Sie ihn dort, wo der Client läuft, nicht auf dem Server:
cd openGym/mcp
npm installFügen Sie ihn anschließend zu claude_desktop_config.json hinzu:
{
"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 ist bei einer Einzelbenutzerinstallation optional, wenn der Server das einzige gefundene Profil automatisch erkennt. Der Server stellt acht Tools bereit: list_routines, get_routine, get_week_plan, list_workouts, get_workout, get_bodyweight, estimate_1rm und muscle_balance. Alle lesen ausschließlich Daten. Kein Tool schreibt Daten. Ein Assistent kann daher beantworten, welches Training Sie letzte Woche protokolliert haben. Er kann jedoch keinen Satz protokollieren, keine Routine bearbeiten und nichts löschen.
Hier liegt der Punkt, den Sie bei einem VPS lösen müssen. OPENGYM_DATA ist ein Dateisystempfad. Ihre Daten liegen auf dem VPS, während Ihr KI-Client auf Ihrem Laptop läuft. Dafür gibt es zwei praktikable Optionen.
- Kopieren Sie die Daten lokal und zeigen Sie dem Server auf die Kopie:
rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/. Setzen Sie anschließendOPENGYM_DATAauf~/opengym-data. Der Server liest die Daten nur, daher geht durch eine Kopie nichts verloren. Führen Sie rsync erneut aus, wenn Sie aktuelle Werte benötigen. - Führen Sie den Server über ssh aus. Setzen Sie
commandaufsshundargsauf["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"]. Dafür muss Node auf dem VPS installiert sein. Außerdem muss die Anmeldung stdout leer lassen, weil stdout als Protokollkanal dient.
Wenn cat data/db.json Permission denied zurückgibt, hat der API-Container diese Dateien als root angelegt und Ihr Benutzer kann sie nicht lesen. Kopieren Sie sie mit sudo oder ändern Sie den Eigentümer auf dem Host. Wenn der Server Netzwerkverbindungen statt stdio annehmen soll, lesen Sie MCP-Server auf einem VPS ausführen.
openGym oder wger: Was sollten Sie betreiben?
wger ist die etablierte Option in diesem Bereich und eine deutlich größere Software. Der Compose-Stack führt gunicorn für eine Django-Anwendung, PostgreSQL, Redis und einen Celery-Worker hinter nginx aus. Dafür erhalten Sie die Erfassung von Ernährung und Zutaten, eine dokumentierte REST API, eine große Datenbank mit Übungen sowie Funktionen für Trainer, die die Trainingspläne anderer Personen verwalten.
openGym besteht aus zwei Containern, einem Ordner mit JSON-Dateien und keinen zu verwaltenden Benutzerkonten außer Passkeys. Das ist der gesamte Unterschied.
Betreiben Sie wger, wenn Sie neben dem Training auch Ihre Ernährung erfassen möchten oder eine API benötigen, auf der Sie aufbauen können. Betreiben Sie openGym, wenn Sie einen Stack möchten, dessen gesamten Code Sie an einem Nachmittag nachvollziehen können, und eine Anmeldung ohne Passwort, das gestohlen werden kann. Der Nachteil dieser Entscheidung ist die geringere Reife: Am 19. August 2026 ist die erste Version von openGym einen Monat alt, während wger auf mehrere Jahre an Veröffentlichungen zurückblickt. Fixieren Sie Ihre Version, bewahren Sie die Backups auf und lesen Sie vor jedem Update die Release Notes.
Wenn Sie noch entscheiden, welche Anwendung auf dem Server Platz verdient, behandelt was sich 2026 für Self-Hosting lohnt die jeweiligen Vor- und Nachteile. Diese Anwendung passt auf demselben kleinen VPS gut neben Mealie für Rezepte oder Actual Budget für Finanzen.
Aktualisieren, ohne Daten zu verlieren
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start api
git fetch --tagsRufen Sie mit git checkout v<new> das gewünschte Release ab. Führen Sie anschließend docker compose up -d --build aus, damit die Container aus diesem Tag neu erstellt werden. Das Backup wird immer zuerst erstellt, weil der Wiederherstellungspfad für JSON-Dateien auf dem Datenträger aus einem einzigen tar-Befehl besteht und nur wenige Sekunden dauert.
FAQ
Warum wird auf meinem Telefon bei openGym nie eine Passkey-Abfrage angezeigt?
Der Browser verweigert das Erstellen einer Anmeldeinformation, weil Sie sich auf http:// oder unter einer reinen IP-Adresse wie http://192.168.1.20:8080 befinden. Browser erlauben Passkeys nur für HTTPS-Ursprünge. localhost ist die einzige Ausnahme. Betreiben Sie openGym hinter einem Reverse Proxy mit einem echten Zertifikat für einen echten Hostnamen. Setzen Sie RP_ID=gym.example.com und ORIGIN=https://gym.example.com in .env. Führen Sie anschließend docker compose up -d aus, damit die Container die neuen Werte übernehmen. Wenn die Abfrage erscheint, die Anmeldung aber verification failed meldet, stimmen diese beiden Werte nicht exakt mit der URL in der Adressleiste überein.
Wo speichert openGym meine Daten, und wie kann ich sie sichern?
Im Verzeichnis ./data neben der Compose-Datei. Es wird als /data in den API-Container eingebunden. Das Verzeichnis enthält db.json für Profile und öffentliche Passkey-Anmeldeinformationen, jeweils eine state-<uid>.json pro Benutzer für Trainingseinheiten und Körpergewicht, secret für den Schlüssel des Sitzungscookies und vapid.json für Push-Benachrichtigungsschlüssel. Sichern Sie die Daten mit docker compose stop api, anschließend mit tar czf ~/opengym-$(date +%F).tar.gz data/ und dann mit docker compose start api. Kopieren Sie das Archiv vom Server. Lassen Sie media/ aus. Dieses Verzeichnis enthält 140 MB Trainingsbilder, die der Medienprozess selbstständig erneut herunterlädt.
Kann Claude meinen openGym-Trainingsverlauf lesen?
Ja, über den optionalen MCP-Server im Verzeichnis mcp/, jedoch ausschließlich lesend. Er stellt acht Werkzeuge für Routinen, Wochenpläne, protokollierte Trainingseinheiten, Körpergewicht, das geschätzte Einer-Wiederholungsmaximum und das Muskelgleichgewicht bereit. Keines dieser Werkzeuge schreibt Daten zurück. Der Server ist kein Container und öffnet keinen Port. Ihr Client startet ihn über stdio und liest die JSON-Dateien direkt unter OPENGYM_DATA. Da es sich um einen Dateisystempfad handelt, müssen Sie bei openGym auf einem VPS entweder eine Kopie von data/ auf den Rechner synchronisieren, auf dem der Client läuft, oder den Server aus der Client-Konfiguration über ssh aufrufen.
Sollte ich openGym oder wger selbst hosten?
Wählen Sie wger, wenn Sie zusätzlich zu Ihrem Trainingsprotokoll Lebensmittel- und Ernährungstracking benötigen oder eine dokumentierte REST-API als Grundlage verwenden möchten. wger verwendet einen größeren Stack: Django unter gunicorn, PostgreSQL, Redis und einen Celery-Worker hinter nginx. Wählen Sie openGym, wenn Sie zwei Container, mit cat lesbare JSON-Dateien und eine Passkey-Anmeldung ohne zu verwaltende Passwörter wünschen. Am 19. August 2026 ist das erste getaggte Release von openGym erst einen Monat alt. Prüfen Sie daher ein git-Tag aus und sichern Sie data/ vor jedem Update.