Problema

Al intentar autoalojar una aplicación web que combina registro de entrenamientos, gestión de usuarios y un módulo opcional de IA, muchos administradores encuentran fallos intermitentes: el contenedor se reinicia, los enlaces de restablecimiento de contraseña llegan con el esquema incorrecto, o los datos desaparecen tras una actualización. El patrón típico es una configuración Docker incompleta o una integración deficiente con el proxy inverso y los volúmenes persistentes. Cuando la aplicación depende de variables como BASE_URL o de secretos para APIs externas, cualquier desalineación entre el entorno de Docker y el front‑end provoca errores de carga, pérdida de sesiones y problemas de sincronización multi‑dispositivo.

Causa

  1. Variables de entorno no sincronizadasBASE_URL, X_FORWARDED_PROTO y los tokens de API deben coincidir con la ruta pública del servicio. Si el proxy reescribe la URL y el contenedor sigue usando la ruta interna, los enlaces generados (invitaciones, restablecimientos) apuntan al host equivocado.

  2. Volúmenes mal montados – LiftTrace guarda la base SQLite y los archivos de respaldo en /data. Un volumen host que se recrea en cada despliegue borra el historial y rompe la sincronización de UUID entre dispositivos.

  3. Proxy inverso sin encabezados correctos – Nginx, Caddy o Traefik deben pasar X‑Forwarded‑Proto y X‑Forwarded‑Host. Sin ellos, la aplicación asume HTTP aunque el tráfico llegue por TLS, generando redirecciones infinitas o enlaces inseguros.

  4. Secretos de Docker no expuestos – Las claves para proveedores de IA, notificaciones o SMTP se suelen pasar como Docker secrets. Si el contenedor se lanza sin --secret, la aplicación entra en modo degradado y desactiva funcionalidades críticas sin aviso visible.

  5. Arquitectura de múltiples nodos – En entornos ARM/AMD mixtos, usar una imagen multi‑arch sin especificar la variante puede lanzar una versión incompatible, provocando fallos al iniciar.

Solución

Adoptar una plantilla Docker‑Compose que cubra los puntos críticos y validar cada capa antes de pasar a producción.

  1. Definir variables estáticas en un archivo .env

    • BASE_URL con la ruta completa (incluye sub‑ruta si se usa).
    • HOST_PORT para mapear el puerto interno 8080.
    • TZ para zona horaria y evitar discrepancias en timestamps.
  2. Montar un volumen persistente nombrado

    docker volume create lifttrace_data
    

    Así el directorio /data se conserva aunque el contenedor se recree.

  3. Configurar Docker secrets

    • Crear archivos ai_key.txt, smtp_user.txt, smtp_pass.txt en un directorio protegido (/run/secrets).
    • Declarar los secretos en docker-compose.yml y referenciarlos con secrets:.
  4. Proxy inverso con encabezados (ejemplo Nginx)

    location /lifttrace/ {
        proxy_pass http://localhost:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    

    La ruta /lifttrace/ coincide con BASE_URL=/lifttrace.

  5. Imagen multi‑arch – Usar la etiqueta latest del repositorio oficial, que incluye amd64 y arm64. En Docker‑Compose especificar platform: linux/amd64 o linux/arm64 según el host.

  6. Backup automatizado – Programar un contenedor ligero (alpine + cron) que comprima /data y lo suba a un bucket S3 o a una ubicación NAS. Mantener al menos dos copias rotativas.

  7. Validar la sincronización de UUID – Después de la primera instalación, crear un registro de prueba desde dos dispositivos diferentes. Si los UUID colisionan, revisar que la variable LIFTTRACE_UUID no esté sobrescrita por un docker-compose.override.yml.

Cuándo aplicar esta solución

  • Síntomas: enlaces de correo con dominio interno, pérdida de historial tras reinicio, contenedor que entra en bucle de reinicio, errores 502/504 del proxy, o notificaciones que nunca llegan.
  • Entornos: homelab con Docker, Raspberry Pi 4/5, servidores NAS con Docker‑Compose, o despliegues en VPS que usan Traefik como gateway.
  • Exclusiones: si la aplicación se ejecuta en Kubernetes con Helm, la lógica de secrets y volúmenes cambia; la guía está orientada a Docker‑Compose puro.

Código

# .env
BASE_URL=/lifttrace
HOST_PORT=8080
TZ=America/Argentina/Buenos_Aires
# docker-compose.yml
version: "3.9"

services:
  lifttrace:
    image: ghcr.io/traceapps/lifttrace:latest
    container_name: lifttrace
    restart: unless-stopped
    ports:
      - "${HOST_PORT}:8080"
    env_file:
      - .env
    environment:
      - BASE_URL=${BASE_URL}
      - TZ=${TZ}
    volumes:
      - lifttrace_data:/data
    secrets:
      - ai_key
      - smtp_user
      - smtp_pass
    # opcional: limitar arquitectura
    # platform: linux/arm64

  backup:
    image: alpine:latest
    container_name: lifttrace_backup
    restart: unless-stopped
    volumes:
      - lifttrace_data:/data:ro
      - ./backups:/backup
    entrypoint: ["/bin/sh","-c"]
    command: |
      "while true; do
         tar -czf /backup/lifttrace_$(date +%F_%H%M).tar.gz -C /data .;
         sleep 86400;
       done"
    depends_on:
      - lifttrace

volumes:
  lifttrace_data:

secrets:
  ai_key:
    file: ./secrets/ai_key.txt
  smtp_user:
    file: ./secrets/smtp_user.txt
  smtp_pass:
    file: ./secrets/smtp_pass.txt

Verificación

  1. Ejecutar docker compose up -d y comprobar que el contenedor está healthy (Dockerfile incluye healthcheck en /health).
  2. Acceder a http://<host_ip>:8080/lifttrace y validar que la página carga sin redirecciones.
  3. Generar un enlace de invitación desde la UI; el correo debe contener https://<dominio>/lifttrace/....
  4. Verificar que el archivo lifttrace_*.tar.gz aparece en ./backups después de 24 h.
  5. Simular una caída del contenedor (docker restart lifttrace) y confirmar que los datos persisten y la sesión no se pierde.

Notas adicionales

  • En entornos con Cloudflare o cualquier CDN, habilitar Proxy en el DNS y añadir la cabecera CF-Visitor al bloque de Nginx evita que el proxy elimine X‑Forwarded‑Proto.
  • Si se usa OIDC (Keycloak, Authelia), la URL de callback debe incluir BASE_URL exactamente; de lo contrario la autenticación falla con invalid_redirect_uri.
  • Para usuarios que no requieren IA, basta con omitir los secretos ai_key; la aplicación detecta la ausencia y desactiva el módulo sin afectar el resto.
  • Cuando se actualiza a una nueva versión, primero detener el servicio, crear una copia de seguridad manual (docker run --rm -v lifttrace_data:/data -v $(pwd):/backup alpine tar -czf /backup/pre_update.tar.gz -C /data .), luego lanzar docker compose pull && docker compose up -d.
  • En Raspberry Pi, asegúrese de que la tarjeta SD tenga al menos 8 GB libres; la base SQLite crece rápidamente con logs de series y notas.