SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-27

Cómo alojar openGym con Docker Compose y TLS

Despliega openGym en un VPS con Docker Compose: fija un tag de Git, configura TLS antes de crear la primera passkey y localiza los datos JSON y el MCP de solo lectura.

Qué se obtiene al alojar openGym por cuenta propia

Puede alojar openGym por cuenta propia clonando el repositorio, editando dos líneas en .env y ejecutando docker compose up -d --build detrás de un proxy inverso que termina TLS (seguridad de la capa de transporte). openGym es un rastreador de gimnasio y peso corporal: planes semanales, entrenamientos guiados, registro de cada serie y evolución del peso. Tiene licencia AGPL-3.0 y almacena todo en archivos JSON sin formato en el disco, por lo que no es necesario ejecutar un servidor de base de datos.

La pila consta de dos contenedores de ejecución prolongada: un contenedor nginx que sirve la compilación de React y un contenedor Node que contiene la API. También incluye un trabajo de ejecución única que descarga unos 140 MB de imágenes y GIF de ejercicios la primera vez que se inicia.

El README del proyecto da a entender dos aspectos que no explica para quien despliega en un servidor público. El inicio de sesión mediante passkey está vinculado a un nombre de host, por lo que el dominio y su certificado deben existir antes del primer inicio de sesión, no después. Además, el servidor MCP opcional es de solo lectura y se ejecuta en la máquina donde funciona el cliente de IA, no dentro de la pila. Esto cambia lo que debe hacer cuando los datos están en un VPS.

openGym es reciente. La primera versión etiquetada, v1.0.0, está fechada el 20 July 2026, y v1.2.7 se publicó el 18 August 2026. Trece etiquetas en aproximadamente un mes indican que la aplicación todavía cambia con frecuencia. Por eso, consulte una etiqueta de versión en lugar de compilar lo que haya en la rama predeterminada.

Planifique el dominio antes del primer inicio de sesión

Las passkeys son el método de inicio de sesión en openGym. Una passkey está vinculada a un relying party ID (RP ID), que es el dominio en el que se creó la credencial, y los navegadores sólo crean passkeys mediante HTTPS. La única excepción es localhost.

Esto tiene una consecuencia que los usuarios descubren en el teléfono. Abra http://203.0.113.10:8080 desde otro dispositivo y no aparecerá ningún aviso para crear una passkey, porque el navegador no permite crear credenciales en un origen HTTP sin cifrar ni en una dirección IP sin nombre de dominio. Las propias notas de resolución de problemas del proyecto indican lo mismo: si no aparece el aviso, está usando http:// o una IP.

El problema es mayor porque el RP ID queda incorporado en todas las credenciales que los usuarios ya han registrado. Si cambia RP_ID más adelante, las passkeys almacenadas en sus dispositivos dejarán de coincidir y nadie podrá iniciar sesión. Decida primero el nombre de host, apunte el DNS al VPS y haga funcionar el certificado antes de que alguien pulse Create profile.

Implementar openGym con Docker Compose

El archivo de Compose monta ./data y ./media mediante bind mount en relación con el propio archivo, por lo que el directorio donde lo clone es su base de datos. Colóquelo en una ubicación persistente.

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

El README todavía muestra una URL de clonación github.com. Esa dirección ya no resuelve, y el repositorio de Gitea anterior es la ubicación activa del proyecto.

Edite .env. En un VPS, importan tres líneas.

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

RP_ID es el nombre de host sin más y ORIGIN es la URL completa, incluido el esquema. Deben coincidir exactamente con lo que aparece en la barra de direcciones; de lo contrario, el inicio de sesión falla con verification failed. El valor WEB_PORT se explica en la sección sobre cómo mantener privado el puerto 8080.

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

docker compose ps debe mostrar web y api en ejecución, y media como terminado con el código 0. Esta salida es correcta: el trabajo de medios tiene restart: "no" porque su tarea consiste en una descarga única. Su registro termina con una línea que comienza por ✓ Exercise media ready, y ls media/img | wc -l debe mostrar unos cientos en lugar de 0. Un directorio vacío indica que la descarga falló; en ese caso, la aplicación muestra tarjetas de ejercicios con imágenes vacías.

La opción --build no es opcional en este caso. El archivo de Compose especifica imágenes precompiladas en ghcr.io que ya no se publican, por lo que docker compose pull falla con denied o manifest unknown y los dos servicios se compilan a partir del código fuente que acaba de clonar. Ambos incluyen una sección build precisamente para eso. Si Compose todavía es nuevo para usted, empiece por Docker Compose en un VPS y vuelva después.

Fije la versión, porque este proyecto es reciente

Como ese espacio de nombres del registro ya no existe, no queda ninguna etiqueta de imagen que fijar. En su lugar, debe fijar la revisión del checkout en disco, porque determina qué versión de la aplicación termina en el contenedor.

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

git status ahora muestra un HEAD separado en esa etiqueta, que es lo que necesita en un servidor. Nada cambia hasta que cambie a otra revisión.

A continuación, indique a Compose que deje de consultar el registro. Coloque esto en docker-compose.override.yml, que Compose carga automáticamente y combina sobre el archivo versionado. Las claves escalares se sustituyen por las del archivo de sobreescritura, por lo que no es necesario modificar nada en git y git pull permanece limpio. Consulte cómo combina Compose un archivo de sobreescritura para conocer todas las reglas de combinación.

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

Con esto configurado, un docker compose up -d posterior compila a partir del código fuente disponible en lugar de fallar al intentar descargar una imagen. Compruebe que la combinación se haya aplicado y vuelva a compilar en esa etiqueta.

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

Terminar TLS con un proxy inverso

Los contenedores usan HTTP sin cifrar. Un componente situado delante debe gestionar el certificado. Caddy es la opción más directa porque solicita y renueva el certificado de Let's Encrypt por sí mismo.

gym.example.com {
    reverse_proxy 127.0.0.1:8080
}

nginx, Traefik y Nginx Proxy Manager funcionan de la misma forma. Cloudflare Tunnel también, y el proyecto lo documenta. No requiere abrir ningún puerto entrante.

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

El comando debe devolver HTTP/2 200 sin mostrar advertencias sobre el certificado. Abra ahora el sitio en un navegador y seleccione Create profile. Si aparece la solicitud de passkey y el inicio de sesión muestra verification failed, RP_ID o ORIGIN no coincide con la URL de la barra de direcciones. Corrija .env y vuelva a ejecutar docker compose up -d. Esto recrea los contenedores para que lean los valores nuevos. Un docker compose restart no vuelve a cargar .env.

Mantener el puerto 8080 fuera de Internet público

De forma predeterminada, el servicio web publica 8080 en todas las interfaces. Por tanto, la aplicación está disponible mediante HTTP sin cifrar en la IP pública, mientras el proxy sirve HTTPS en el mismo equipo. Una regla del firewall no corrige esto. Docker publica un puerto mediante una regla DNAT en la tabla nat. Después, ese tráfico se procesa en la cadena FORWARD, donde las reglas propias de Docker lo aceptan, mientras que las reglas de ufw se encuentran en la ruta INPUT. Por tanto, sudo ufw deny 8080/tcp no bloquea nada.

La solución es publicar el puerto sólo en la dirección de loopback. El archivo compose asigna "${WEB_PORT:-8080}:${NGINX_PORT:-80}". El valor que establezca en WEB_PORT se sustituye en el lado izquierdo de esa asignación, y la sintaxis abreviada de Docker acepta allí un par ip:port. Por eso WEB_PORT=127.0.0.1:8080 funciona.

docker compose config
sudo ss -ltnp | grep 8080

En la configuración combinada, dentro de ports del servicio web, debe aparecer host_ip: 127.0.0.1. ss debe mostrar 127.0.0.1:8080 y no 0.0.0.0:8080. Desde otro equipo, curl http://<your-vps-ip>:8080 debe rechazarse o agotar el tiempo de espera, mientras el nombre de host HTTPS sigue funcionando.

Cierre el registro cuando exista su perfil

El registro está abierto de forma predeterminada y el modo de invitado está activado. En un nombre de host público, cualquiera que encuentre la URL puede crear un perfil en su servidor. Registre primero su propio perfil y, después, busque su ID de usuario: ls data/ muestra un archivo llamado state-<uid>.json para cada usuario, y ese <uid> es el valor que necesita.

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

Ejecute docker compose up -d de nuevo. Settings muestra ahora un panel de administración donde puede generar y revocar códigos de invitación. Así, las personas con las que entrena pueden registrarse y nadie más puede hacerlo. openGym no conoce los proveedores de identidad externos. Por tanto, esos códigos de invitación controlan únicamente esta aplicación y ninguna otra del servidor. Si prefiere asignar una sola cuenta a cada persona para todos los servicios que ejecuta, coloque Authentik delante como proxy de forward auth. De este modo, se controla el nombre de host antes de que se cargue el inicio de sesión con passkey de openGym.

Dónde se almacenan los datos y qué copia de seguridad los protege

Todo está en el directorio ./data, montado en el contenedor de la API en /data. Hay cuatro tipos de archivo: db.json contiene los perfiles y las credenciales públicas de passkeys, state-<uid>.json contiene las rutinas, los entrenamientos y el peso corporal de un usuario, secret es la clave de la cookie de sesión y vapid.json contiene las claves de notificaciones push generadas durante la primera ejecución.

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

Detenga primero la API porque tar copia archivos mientras la API puede estar escribiendo en uno, y un archivo JSON copiado parcialmente se restaura como un archivo JSON dañado. Detenerla y volver a iniciarla tarda unos dos segundos. Después, copie el archivo comprimido fuera del servidor, porque un archivo comprimido almacenado en el VPS no sobrevive a la pérdida del VPS. Excluya media/ de la copia de seguridad: ocupa 140 MB de imágenes de ejercicios que el trabajo de medios vuelve a descargar gratuitamente.

Para restaurar, extraiga el archivo en la misma ruta, en un host que sirva el mismo dominio. Una passkey almacenada en el teléfono está vinculada al RP ID con el que se creó, por lo que restaurar en un nombre de host nuevo le deja una base de datos operativa, pero nadie puede iniciar sesión. Mantenga el dominio o planifique volver a registrar todas las passkeys. La misma disciplina se aplica al resto de servicios que ejecute, y hacer copias de seguridad y actualizar una pila de Docker Compose cubre el procedimiento general.

El servidor MCP es de solo lectura y se ejecuta en su máquina

MCP (model context protocol) es la forma en que un cliente como Claude Desktop o Cursor se comunica con un servidor de herramientas local. openGym incluye uno en mcp/. No forma parte del archivo compose, no es un contenedor y no escucha en ningún puerto. El cliente lo inicia como un proceso hijo y se comunica con él mediante stdio. Por eso el README indica que nunca sale de su máquina.

Instálelo donde se ejecuta el cliente, no en el servidor:

cd openGym/mcp
npm install

Después, añádalo a 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 es opcional en una instalación para un solo usuario, porque el servidor detecta el único perfil que encuentra. Expone ocho herramientas: list_routines, get_routine, get_week_plan, list_workouts, get_workout, get_bodyweight, estimate_1rm y muscle_balance. Todas leen. Ninguna escribe. Por tanto, un asistente puede responder qué entrenó la semana pasada, pero no puede registrar una serie, editar una rutina ni eliminar nada. Esta lista es un ejemplo compacto de una decisión a la que vuelve continuamente el diseño de agentes: las herramientas que expone determinan todo lo que puede hacer un modelo. Aprender cómo funcionan los agentes escribiendo usted mismo el bucle es la forma más rápida de entender por qué un conjunto de herramientas de solo lectura es una decisión de diseño y no una limitación.

Esta es la parte que debe resolver quien usa un VPS. OPENGYM_DATA es una ruta del sistema de archivos, y sus datos están en el VPS mientras el cliente de IA está en su portátil. Hay dos opciones que reflejan esta situación sin ocultarla.

  1. Copie los datos y configure el servidor para que use la copia: rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/. Después, establezca OPENGYM_DATA en ~/opengym-data. El servidor sólo lee, por lo que una copia no pierde información. Vuelva a ejecutar rsync cuando necesite datos actualizados.
  2. Ejecute el servidor mediante ssh, con command establecido en ssh y args establecido en ["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"]. Node debe estar instalado en el VPS. Además, el inicio de sesión no debe imprimir nada en stdout, porque stdout es el canal del protocolo.

Ambas opciones suponen que el propio agente se ejecuta en su portátil. Si prefiere que se ejecute en el mismo servidor que los datos, OneCLI proporciona a cada persona un agente aislado en el servidor, de modo que el salto stdio de vuelta a data/ vuelve a ser local.

Si cat data/db.json devuelve Permission denied, el contenedor de API creó esos archivos como root y su cuenta no puede leerlos. Cópielos con sudo o cambie su propietario en el host. Para servidores que deben escuchar en la red en lugar de usar stdio, consulte Ejecutar servidores MCP en un VPS.

openGym o wger: ¿cuál debería ejecutar?

wger es la opción consolidada en este nicho y es un componente de software mucho más grande. Su stack de Compose ejecuta gunicorn para servir una aplicación Django, PostgreSQL, Redis y un worker de Celery detrás de nginx. A cambio, ofrece seguimiento de la nutrición y los ingredientes, una API REST documentada, una base de datos comunitaria amplia de ejercicios y funciones para entrenadores que gestionan los planes de otras personas.

openGym consta de dos contenedores, una carpeta de archivos JSON y ninguna cuenta que administrar aparte de las passkeys. Esa es toda la diferencia. Si alguna vez mantuvo una instalación de Chatwoot, donde una copia de seguridad consiste en un volcado de Postgres junto con el directorio de uploads y cada actualización de versión ejecuta migraciones de base de datos, ya sabe qué implica mantener wger.

Ejecute wger si quiere llevar el seguimiento de la alimentación junto con el entrenamiento o si necesita una API sobre la que desarrollar. Ejecute openGym si quiere un stack lo bastante pequeño como para leerlo de principio a fin en una tarde y un inicio de sesión sin contraseña que filtrar. El coste de esa elección es la madurez: a 19 August 2026, la primera versión de openGym tiene un mes, mientras que wger acumula años de versiones. Fije la versión, conserve las copias de seguridad y lea las notas de la versión antes de cada actualización.

Si todavía está decidiendo qué merece espacio en el servidor, qué merece la pena alojar por cuenta propia en 2026 explica las ventajas y desventajas, y esta aplicación encaja bien junto a Mealie para recetas o Actual Budget para las finanzas en el mismo VPS pequeño.

Actualización sin perder nada

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

Obtenga la versión que necesita con git checkout v<new> y ejecute después docker compose up -d --build para reconstruir los contenedores a partir de esa etiqueta. La copia de seguridad se hace siempre antes, porque la restauración de los archivos JSON almacenados en disco requiere un solo comando tar y tarda unos segundos.

FAQ

¿Por qué openGym nunca muestra un aviso para usar una passkey en mi teléfono?

El navegador no puede crear una credencial porque está en http:// o en una dirección IP sin nombre de host, como http://192.168.1.20:8080. Los navegadores sólo permiten passkeys en orígenes HTTPS, con localhost como única excepción. Coloque openGym detrás de un reverse proxy que tenga un certificado válido para un nombre de host real, establezca RP_ID=gym.example.com y ORIGIN=https://gym.example.com en .env y ejecute docker compose up -d para que los contenedores adopten los valores nuevos. Si aparece el aviso, pero el inicio de sesión muestra verification failed, esos dos valores no coinciden exactamente con la URL de la barra de direcciones.

¿Dónde almacena openGym mis datos y cómo puedo hacer una copia de seguridad?

En el directorio ./data situado junto al archivo compose, montado en el contenedor de la API como /data. Contiene db.json para los perfiles y las credenciales públicas de passkeys, un archivo state-<uid>.json por usuario para los entrenamientos y el peso corporal, secret para la clave de las cookies de sesión y vapid.json para las claves de las notificaciones push. Haga la copia de seguridad con docker compose stop api, después tar czf ~/opengym-$(date +%F).tar.gz data/ y luego docker compose start api, y copie el archivo comprimido fuera del servidor. Omita media/, que contiene 140 MB de imágenes de ejercicios que el trabajo de medios vuelve a descargar por su cuenta.

¿Puede Claude leer mi historial de entrenamientos de openGym?

Sí, mediante el servidor MCP opcional del directorio mcp/, y sólo para lectura. Expone ocho herramientas para consultar rutinas, planes semanales, entrenamientos registrados, peso corporal, una repetición máxima estimada y el equilibrio muscular; ninguna de ellas escribe datos. No es un contenedor ni abre ningún puerto: el cliente lo inicia mediante stdio y lee directamente los archivos JSON de OPENGYM_DATA. Como se trata de una ruta del sistema de archivos, ejecutar openGym en un VPS implica sincronizar una copia de data/ con el equipo que ejecuta el cliente o invocar el servidor mediante ssh desde la configuración del cliente.

¿Debería alojar openGym o wger en mi propio servidor?

Elija wger si quiere registrar la alimentación y la nutrición junto con sus entrenamientos, o si necesita una API REST documentada sobre la que desarrollar. Ejecuta una pila más grande: Django con gunicorn, PostgreSQL, Redis y un trabajador de Celery detrás de nginx. Elija openGym si quiere dos contenedores, archivos JSON que pueda leer con cat y un inicio de sesión con passkey sin gestionar contraseñas. Al 19 de agosto de 2026, la primera versión etiquetada de openGym tiene un mes, por lo que debe comprobar una etiqueta de git y hacer una copia de seguridad de data/ antes de cada actualización.