openGym zelf hosten met Docker Compose
Implementeer openGym op uw eigen VPS. Leer hoe u TLS configureert voor passkeys, waar de JSON-data wordt opgeslagen en hoe u de read-only MCP-server correct instelt.
Wat u krijgt wanneer u openGym zelf host
U host openGym zelf door de repository te klonen, twee regels in .env aan te passen en docker compose up -d --build uit te voeren achter een reverse proxy die TLS (transport layer security) afhandelt. openGym is een tracker voor sportschool- en lichaamsgewichtoefeningen: wekelijkse schema's, begeleide trainingen, registratie van elke set en gewichtsverloop. Het is gelicentieerd onder AGPL-3.0 en slaat alles op in eenvoudige JSON-bestanden op uw schijf, waardoor er geen databaseserver nodig is.
De stack bestaat uit twee langlopende containers: een nginx-container die de React-build serveert en een Node-container die de API bevat, plus een eenmalige taak die bij de eerste start ongeveer 140 MB aan oefeningsafbeeldingen en GIF's downloadt.
Twee zaken die de README van het project impliceert, maar niet expliciet benoemt voor iemand die op een publieke server implementeert. Passkey-login is gebonden aan een hostnaam, dus het domein en het bijbehorende certificaat moeten bestaan vóór de eerste login, niet erna. En de optionele MCP-server is alleen-lezen en draait op de machine waar uw AI-client draait, niet binnen de stack; dit verandert uw werkwijze wanneer de data op een VPS staat.
openGym is nog jong. De eerste getagde release, v1.0.0, is gedateerd op 20 juli 2026, en v1.2.7 verscheen op 18 augustus 2026. Dertien tags in ongeveer een maand tijd betekent dat de applicatie nog volop in ontwikkeling is; check daarom een release-tag uit in plaats van de code op de standaard branch te bouwen.
Plan het domein vóór de eerste aanmelding
Passkeys zijn de manier waarop u zich aanmeldt bij openGym. Een passkey is gekoppeld aan een relying party ID (RP ID), wat het domein is waarop de credential is aangemaakt. Browsers maken passkeys uitsluitend aan via HTTPS. De enige uitzondering is localhost.
Dit heeft een consequentie waar gebruikers op hun telefoon tegenaan lopen. Open http://203.0.113.10:8080 vanaf een ander apparaat en er verschijnt helemaal geen prompt voor een passkey, omdat de browser weigert een credential aan te maken op een HTTP-oorsprong of een kaal IP-adres. De eigen probleemoplossingsnotities van het project vermelden hetzelfde: geen prompt betekent dat u zich op http:// bevindt of op een IP-adres.
Erger nog, het RP ID is verankerd in elke credential die uw gebruikers al hebben geregistreerd. Wijzig RP_ID op een later moment en de passkeys die op hun apparaten zijn opgeslagen, komen niet langer overeen, waardoor niemand zich meer kan aanmelden. Bepaal eerst de hostnaam, wijs DNS naar de VPS en zorg dat het certificaat werkt voordat iemand op Create profile tikt.
openGym implementeren met Docker Compose
Het compose-bestand voert een bind-mount uit van ./data en ./media relatief aan het bestand zelf; de map waarin u het project kloont is dus uw database. Plaats deze op een duurzame locatie.
sudo install -d -o "$USER" -g "$USER" /opt/opengym
git clone https://gitea.com/DuarteSantos/openGym /opt/opengym
cd /opt/opengym
cp .env.example .envDe README toont nog steeds een github.com kloon-URL. Dat adres is niet langer bereikbaar; de bovenstaande Gitea-repository is de actuele thuisbasis van het project.
Bewerk .env. Op een VPS zijn drie regels van belang.
RP_ID=gym.example.com
ORIGIN=https://gym.example.com
WEB_PORT=127.0.0.1:8080RP_ID is de kale hostnaam en ORIGIN is de volledige URL inclusief het schema. Deze moeten exact overeenkomen met de adresbalk, anders mislukt het inloggen met verification failed. De waarde WEB_PORT wordt toegelicht in de sectie over het privé houden van poort 8080.
docker compose up -d --build
docker compose ps
docker compose logs mediadocker compose ps hoort web en api als 'running' te tonen, en media als 'exited' met code 0. Die exit is correct: de media-taak heeft restart: "no" omdat het werk een eenmalige download betreft. Het logbestand eindigt met een regel die begint met ✓ Exercise media ready, en ls media/img | wc -l hoort enkele honderden te tonen in plaats van 0. Een lege map betekent dat de download is mislukt, waarna de applicatie oefenkaarten met lege afbeeldingen rendert.
De vlag --build is hier niet optioneel. Het compose-bestand verwijst naar voorgebouwde images op ghcr.io die niet langer worden gepubliceerd, waardoor docker compose pull faalt met denied of manifest unknown. De twee services worden daarom gebouwd vanuit de broncode die u zojuist heeft gekloond. Beide bevatten hiervoor een build-sectie. Als Compose nieuw voor u is, begin dan bij Docker Compose op een VPS en keer daarna terug.
Zet de versie vast, aangezien dit project nog jong is
Omdat die registry-namespace niet meer bestaat, is er geen image-tag meer om vast te zetten. Wat u in plaats daarvan vastzet, is de checkout op schijf, aangezien deze bepaalt welke versie van de applicatie in de container terechtkomt.
cd /opt/opengym
git fetch --tags
git checkout v1.2.7git status rapporteert nu een detached HEAD bij die tag, wat precies is wat u op een server wilt. Er verandert niets totdat u een andere versie uitcheckt.
Instrueer Compose vervolgens om de registry helemaal niet meer te benaderen. Plaats dit in docker-compose.override.yml, dat Compose automatisch laadt en samenvoegt met het gevolgde bestand. Scalaire sleutels worden vervangen door de override, waardoor er niets in git bewerkt hoeft te worden en git pull schoon blijft. Zie hoe Compose een override-bestand samenvoegt voor de volledige samenvoegregels.
services:
api:
pull_policy: build
web:
pull_policy: buildWanneer dit is ingesteld, bouwt een latere docker compose up -d vanuit de broncode die u heeft in plaats van te falen op een pull. Controleer of de samenvoeging effect heeft gehad en herbouw vervolgens op de tag.
docker compose config | grep pull_policy
docker compose up -d --buildTLS-termination met een reverse proxy
De containers communiceren via standaard HTTP. Een component aan de voorzijde moet het certificaat beheren. Caddy is de snelste methode, omdat deze automatisch certificaten aanvraagt en vernieuwt bij Let's Encrypt.
gym.example.com {
reverse_proxy 127.0.0.1:8080
}nginx, Traefik en Nginx Proxy Manager werken op dezelfde wijze. Dit geldt ook voor een Cloudflare Tunnel, die in de projectdocumentatie wordt beschreven en waarvoor geen inkomende poorten hoeven te worden geopend.
curl -sI https://gym.example.com | head -1Dit zou HTTP/2 200 moeten retourneren zonder certificaatwaarschuwing. Open nu de site in een browser en tik op Create profile. Als de passkey-prompt verschijnt en de login vervolgens verification failed rapporteert, komt RP_ID of ORIGIN niet overeen met de URL in de adresbalk. Corrigeer .env en voer docker compose up -d opnieuw uit; hiermee worden de containers opnieuw aangemaakt zodat ze de nieuwe waarden inlezen. Een docker compose restart herlaadt .env niet.
Houd poort 8080 buiten het publieke internet
Standaard publiceert de webservice 8080 op elke interface. Hierdoor is de applicatie bereikbaar via onversleuteld HTTP op uw publieke IP-adres, terwijl de proxy HTTPS op dezelfde machine afhandelt. Een firewallregel lost dit niet op. Docker publiceert een poort met een DNAT-regel in de nat-tabel. Dat verkeer wordt vervolgens afgehandeld in de FORWARD-chain, waar de eigen regels van Docker het accepteren, terwijl de regels van ufw zich op het INPUT-pad bevinden. sudo ufw deny 8080/tcp blokkeert daarom niets.
De oplossing is om alleen op het loopback-adres te publiceren. Het compose-bestand mapt "${WEB_PORT:-8080}:${NGINX_PORT:-80}", dus alles wat u instelt in WEB_PORT wordt vervangen aan de linkerzijde van die mapping. De korte syntaxis van Docker accepteert daar een ip:port-paar. Daarom werkt WEB_PORT=127.0.0.1:8080.
docker compose config
sudo ss -ltnp | grep 8080In de samengevoegde configuratie, onder de ports van de webservice, wilt u host_ip: 127.0.0.1 zien. ss zou 127.0.0.1:8080 moeten tonen en niet 0.0.0.0:8080. Vanaf een andere machine zou curl http://<your-vps-ip>:8080 nu geweigerd moeten worden of een time-out moeten geven, terwijl de HTTPS-hostnaam blijft werken.
Registratie sluiten zodra uw profiel bestaat
Registratie staat standaard open en de gastmodus is ingeschakeld. Op een publieke hostnaam betekent dit dat iedereen die de URL vindt, een profiel op uw server kan aanmaken. Registreer eerst uw eigen profiel en zoek vervolgens uw gebruikers-ID op: ls data/ toont een bestand genaamd state-<uid>.json voor elke gebruiker, en die <uid> is de waarde die u nodig heeft.
ADMIN_UIDS=<your-uid>
INVITE_ONLY=1
ALLOW_GUEST=0Voer docker compose up -d opnieuw uit. Bij de instellingen verschijnt nu een beheerdersdashboard waar u uitnodigingscodes kunt genereren en intrekken, zodat alleen de mensen met wie u traint zich kunnen registreren. openGym ondersteunt geen externe identiteitsproviders; deze uitnodigingscodes beheren dus uitsluitend deze applicatie en niets anders op de server. Als u liever één account per persoon gebruikt voor alle services die u draait, kunt u Authentik als forward auth proxy voor de applicatie plaatsen. Dit beveiligt de hostnaam voordat het eigen passkey-inlogscherm van openGym wordt geladen.
Waar de data zich bevindt en de back-up die deze beschermt
Alles staat in de map ./data, die in de API-container is gemount op /data. Er zijn vier soorten bestanden: db.json bevat profielen en publieke passkey-referenties, state-<uid>.json bevat de routines, workouts en het lichaamsgewicht van één gebruiker, secret is de sleutel voor sessiecookies en vapid.json bevat de push-notificatiesleutels die bij de eerste uitvoering worden gegenereerd.
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start apiStop eerst de API, omdat tar bestanden kopieert terwijl de API mogelijk naar een bestand schrijft; een half gekopieerd JSON-bestand wordt na herstel een corrupt JSON-bestand. Het stoppen en starten duurt ongeveer twee seconden. Kopieer het archief daarna van de server af, want een archief dat op de VPS blijft staan, overleeft het niet als de VPS uitvalt. Laat media/ buiten de back-up: dit is 140 MB aan oefeningsafbeeldingen die de media-job kosteloos opnieuw downloadt.
Herstellen betekent het uitpakken van het archief naar hetzelfde pad op een host die hetzelfde domein bedient. Een passkey die op uw telefoon is opgeslagen, is gekoppeld aan de RP ID waarmee deze is aangemaakt. Een herstel naar een nieuwe hostnaam levert dus een werkende database op waar niemand op kan inloggen. Behoud het domein of houd er rekening mee dat u elke passkey opnieuw moet registreren. Dezelfde discipline geldt voor alles wat u draait, en het maken van back-ups en upgraden van een Docker Compose-stack behandelt de algemene procedure.
De MCP-server is alleen-lezen en draait op uw eigen machine
MCP (model context protocol) is de manier waarop een client zoals Claude Desktop of Cursor communiceert met een lokale tool-server. openGym levert er een mee in mcp/. Het maakt geen deel uit van het compose-bestand, het is geen container en het luistert op geen enkele poort. De client start het als een onderliggend proces en communiceert via stdio; daarom staat in de README dat het uw machine nooit verlaat.
Installeer het waar de client draait, niet op de server:
cd openGym/mcp
npm installVoeg het vervolgens toe aan 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 is optioneel bij een installatie voor één gebruiker, waarbij de server het enige profiel detecteert dat het vindt. Het stelt acht tools beschikbaar: list_routines, get_routine, get_week_plan, list_workouts, get_workout, get_bodyweight, estimate_1rm en muscle_balance. Elk van deze tools leest alleen. Geen van deze tools schrijft gegevens, dus een assistent kan wel beantwoorden wat u vorige week hebt getraind, maar kan geen set loggen, een routine bewerken of iets verwijderen.
Dit is het punt dat een VPS-gebruiker moet oplossen. OPENGYM_DATA is een bestandssysteempad en uw gegevens staan op de VPS, terwijl uw AI-client op uw laptop staat. Twee opties die hier eerlijk over zijn:
- Kopieer de gegevens naar uw lokale machine en laat de server naar de kopie wijzen:
rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/, en stel vervolgensOPENGYM_DATAin op~/opengym-data. De server leest alleen, dus bij een kopie gaat niets verloren. Voer de rsync opnieuw uit wanneer u over actuele cijfers wilt beschikken. - Draai de server via ssh, met
commandingesteld opsshenargsingesteld op["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"]. Hiervoor moet Node op de VPS zijn geïnstalleerd en is een login vereist die niets naar stdout schrijft, omdat stdout het protocolkanaal is.
Als cat data/db.json de waarde Permission denied retourneert, heeft de API-container die bestanden als root geschreven en kan uw login ze niet lezen. Kopieer ze met sudo of wijzig het eigenaarschap op de host. Voor servers die bedoeld zijn om via het netwerk te luisteren in plaats van via stdio, zie MCP-servers draaien op een VPS.
openGym of wger: welke moet u draaien?
wger is de gevestigde optie in deze niche en het is een aanzienlijk groter stuk software. De compose-stack draait gunicorn die een Django-applicatie bedient, PostgreSQL, Redis en een Celery-worker achter nginx. In ruil daarvoor krijgt u tracking voor voeding en ingrediënten, een gedocumenteerde REST API, een grote database met oefeningen van de community en functies voor trainers die de schema's van anderen beheren.
openGym bestaat uit twee containers, een map met JSON-bestanden en geen accounts om te beheren, behalve passkeys. Dat is het volledige verschil.
Draai wger als u voeding naast uw training wilt bijhouden, of als u een API nodig heeft om tegenaan te bouwen. Draai openGym als u een stack wilt die klein genoeg is om in een middag van begin tot eind door te lezen, en een login zonder wachtwoord dat kan lekken. De prijs voor die keuze is volwassenheid: op 19 augustus 2026 is de eerste release van openGym een maand oud, terwijl wger jaren aan releases achter de rug heeft. Pin uw versie, bewaar de back-ups en lees de release notes voor elke update.
Als u nog beslist wat ruimte op de server verdient, behandelt wat is de moeite waard om zelf te hosten in 2026 de afwegingen, en deze app past uitstekend naast Mealie voor recepten of Actual Budget voor financiën op dezelfde kleine VPS.
Updaten zonder gegevensverlies
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start api
git fetch --tagsCheck de gewenste release uit met git checkout v<new> en voer vervolgens docker compose up -d --build uit, zodat de containers opnieuw worden opgebouwd op basis van die tag. Maak altijd eerst een back-up, aangezien het herstellen van JSON-bestanden op schijf slechts één tar-commando vereist en enkele seconden in beslag neemt.
FAQ
Waarom toont openGym nooit een passkey-prompt op mijn telefoon?
De browser weigert een credential aan te maken omdat u zich op http:// bevindt of op een kaal IP-adres, zoals http://192.168.1.20:8080. Browsers staan passkeys alleen toe op HTTPS-origins, met localhost als enige uitzondering. Plaats openGym achter een reverse proxy die een geldig certificaat voor een echte hostnaam beheert, stel RP_ID=gym.example.com en ORIGIN=https://gym.example.com in .env in, en voer docker compose up -d uit zodat de containers de nieuwe waarden overnemen. Als de prompt verschijnt maar de login meldt verification failed, dan komen die twee waarden niet exact overeen met de URL in de adresbalk.
Waar slaat openGym mijn gegevens op en hoe maak ik een back-up?
In de map ./data naast het compose-bestand, gemount in de API-container als /data. Deze bevat db.json voor profielen en publieke passkey-credentials, één state-<uid>.json per gebruiker voor trainingen en lichaamsgewicht, secret voor de sessie-cookie-sleutel, en vapid.json voor push-notificatiesleutels. Maak een back-up met docker compose stop api, daarna tar czf ~/opengym-$(date +%F).tar.gz data/, vervolgens docker compose start api, en kopieer het archief van de server af. Sla media/ over; dit is 140 MB aan oefeningsafbeeldingen die de media-job zelf opnieuw downloadt.
Kan Claude mijn openGym-trainingsgeschiedenis lezen?
Ja, via de optionele MCP-server in de map mcp/, en uitsluitend voor leesdoeleinden. Deze stelt acht tools beschikbaar voor routines, weekplannen, gelogde trainingen, lichaamsgewicht, geschatte one-rep max en spierbalans; er wordt niets teruggeschreven. Het is geen container en opent geen poort: uw client start het via stdio en het leest de JSON-bestanden op OPENGYM_DATA direct. Omdat dit een bestandspad is, betekent het draaien van openGym op een VPS dat u ofwel een kopie van data/ synchroniseert naar de machine waarop de client draait, of de server aanroept via ssh vanuit de client-configuratie.
Moet ik openGym of wger zelf hosten?
Kies wger als u naast uw trainingslogboek ook voeding wilt bijhouden, of als u een gedocumenteerde REST API nodig heeft om op voort te bouwen. Het draait een grotere stack: Django onder gunicorn, PostgreSQL, Redis en een Celery-worker achter nginx. Kies openGym als u twee containers wilt, JSON-bestanden die u kunt lezen met cat, en passkey-login zonder wachtwoordbeheer. Sinds 19 augustus 2026 is de eerste getagde release van openGym één maand oud; check daarom een git-tag uit en maak een back-up van data/ vóór elke update.