Saltar al contenido principal

Despliegue con Docker Compose

Esta página describe el despliegue de FoxPlan en un servidor único con Docker Compose, detrás de Traefik, que se encarga del enrutamiento y de la terminación TLS.

Para un entorno de producción con escalado y alta disponibilidad, consulte Despliegue en Kubernetes.

Dos versiones​

Prueba localInstalación empresarial
ParaEvaluar FoxPlan en un puesto de trabajoUn servidor compartido, la producción
Archivosun solo docker-compose.ymldocker-compose.yml, install.sh y archivos .env
ConfiguraciónNingunaDominio, TLS, SMTP, secretos
Accesohttp://localhost:8090, solo desde el puestohttps:// su dominio
EmailsCapturados por un buzón de prueba integradoEnviados por su servidor SMTP

Ambas versiones utilizan las mismas imágenes y se instalan en dos pasos distintos:

  1. Descargar las imágenes: el único momento en que se necesita acceso a Internet, desde el propio servidor o desde una máquina de transferencia;
  2. Puesta en servicio — prueba local o instalación empresarial: la pila arranca únicamente con las imágenes ya presentes. No se contacta con ningún registro; si falta una imagen, no arranca nada.
Registro de imágenes de FoxPlan

La dirección del registro se la comunica FoxPlan. Indíquela en la variable FOXPLAN_REGISTRY, en el archivo .env situado junto al docker-compose.yml — tanto para la prueba como para la instalación empresarial. El acceso de lectura es libre: no se necesita ninguna credencial ni docker login.

ImagenFunción
foxplan_prodAplicación
foxplan_prod-docsEsta documentación
foxplan_prod-exportExportación de las planificaciones (PDF, PNG, Excel, iCal)
foxplan_prod-mspImportación y exportación MS Project y Primavera

latest sigue la última versión publicada; cada versión lleva además una etiqueta inmutable, que conviene fijar en producción. Las imágenes de terceros (MongoDB, Redis, Traefik…) se descargan desde Docker Hub, no desde este registro.

Paso 1: descargar las imágenes​

La descarga de las imágenes es el único momento en que la pila necesita acceder a Internet. Una vez que las imágenes están en el servidor, FoxPlan ya no contacta con ningún registro: ni al arrancar, ni durante el funcionamiento, ni al reiniciar un contenedor.

Imágenes a descargar​

Las cuatro imágenes de FoxPlan indicadas más arriba, en la versión que despliega, y las siguientes imágenes de terceros, todas descargadas de Docker Hub:

ImagenPrueba localInstalación empresarial
traefik:v3.6.4SíSí
mongo:8.2.7SíSí (MONGO_IMAGE)
redis:7.4SíSí (REDIS_IMAGE)
axllent/mailpit:v1.27SíNo: sus correos salen por su servidor SMTP
dxflrs/garage:v2.1.0SíSolo si añade Garage — consulte Almacenamiento de archivos
curlimages/curl:8.14.1Sí: inicializa Garage en el primer arranqueNo
rclone/rclone:1.68.2NoSolo para hacer copia de seguridad de Garage

Con su propio almacenamiento S3, no se necesitan ni Garage, ni curl, ni rclone. Si cambia MONGO_IMAGE o REDIS_IMAGE en el .env, descargue la imagen que indique en ellas.

Flujos de red​

Flujos salientes emitidos por el motor Docker durante la descarga, todos en HTTPS (TCP/443):

DestinoFunciónImágenes afectadas
Host del registro de FoxPlanÍndice de las imágenesfoxplan_prod, -docs, -export, -msp
Servicio de tokens del registro de FoxPlanToken de acceso anónimoídem
Almacenamiento de objetos del registro de FoxPlanCapas de las imágenes: el registro redirige las descargas hacia este almacenamientoídem
registry-1.docker.io, auth.docker.ioDocker Hub: índice de las imágenes y token de accesoImágenes de terceros
production.cloudflare.docker.com, production.cloudfront.docker.comCapas de las imágenes de Docker Hub (CDN)ídem
app.fox-plan.comDescarga de los archivos de despliegue de esta páginaPrueba local (curl)

Si su cortafuegos filtra por imagen (proxy de registro, lista blanca de Harbor, Nexus, Artifactory…), autorice los nombres y versiones anteriores.

Los tres hosts del registro de FoxPlan se le comunican junto con su dirección. Un firewall que solo autoriza el dominio del registro deja pasar el índice pero bloquea las capas: el pull se queda entonces bloqueado o falla por tiempo de espera agotado. Las redirecciones hacia el almacenamiento de objetos y el CDN también deben autorizarse.

Los flujos de funcionamiento — autoridad de certificación ACME, servidor SMTP, almacenamiento S3, proveedores SSO — se describen en la matriz de flujos de red.

Desde el servidor​

Si el servidor puede acceder a los registros mientras dura la descarga:

FOXPLAN_REGISTRY=<dirección comunicada por FoxPlan>
V=latest # en producción: la versión fijada en FOXPLAN_VERSION
IMAGES=(traefik:v3.6.4 mongo:8.2.7 redis:7.4)
for i in foxplan_prod foxplan_prod-docs foxplan_prod-export foxplan_prod-msp; do
IMAGES+=("$FOXPLAN_REGISTRY/$i:$V")
done
# Prueba local: añada el buzón de prueba y el almacenamiento integrado.
# IMAGES+=(axllent/mailpit:v1.27 dxflrs/garage:v2.1.0 curlimages/curl:8.14.1)
# Instalación empresarial con Garage:
# IMAGES+=(dxflrs/garage:v2.1.0 rclone/rclone:1.68.2)
for i in "${IMAGES[@]}"; do
case "$i" in
*-export:*|*-msp:*) docker pull --platform linux/amd64 "$i" ;;
*) docker pull "$i" ;;
esac || echo "ÉCHEC : $i"
done

Las imágenes foxplan_prod-export y foxplan_prod-msp solo existen para amd64: el bucle siempre las descarga para esa arquitectura. En un servidor ARM o un Mac con Apple Silicon, se ejecutan en emulación. Una línea ÉCHEC indica una imagen no descargada: corríjalo antes de pasar al paso 2.

Después pueden volver a cerrarse los accesos a los registros: el paso 2 ya no los necesita.

Desde un entorno independiente​

Sector sensible: obtenga las imágenes fuera de su sistema de información

Si su organización pertenece a un sector sensible — defensa, sanidad, energía, finanzas, administración pública, operador de importancia vital — no abra estos flujos desde el servidor de producción ni desde su red interna.

Obtenga las imágenes en una máquina independiente de su sistema de información (puesto o entorno de transferencia dedicado, sin acceso a sus redes internas), contrólelas y después introdúzcalas por su circuito habitual: registro interno, o archivo transferido en un soporte controlado.

# 1. En la máquina de transferencia, fuera de su sistema de información.
# Componga IMAGES exactamente como más arriba (prueba o empresa, con o sin Garage):
# una imagen olvidada aquí faltará en el arranque.
# --platform: la arquitectura de su servidor de destino (amd64 en la mayoría de los casos).
for i in "${IMAGES[@]}"; do docker pull --platform linux/amd64 "$i"; done
docker save -o foxplan-images.tar "${IMAGES[@]}"
sha256sum foxplan-images.tar > foxplan-images.tar.sha256

# 2. Controles según su política: antivirus, análisis de vulnerabilidades
# (Trivy, Grype…), huellas de las imágenes (docker images --digests).

# 3. En el servidor de destino, sin acceso a Internet.
sha256sum -c foxplan-images.tar.sha256
docker load -i foxplan-images.tar

Las imágenes cargadas conservan su nombre original: el paso 2 las encuentra en local. Si utiliza un registro interno, vuelva a etiquetarlas hacia él (docker tag, docker push), descárguelas desde el servidor e indique ese registro en FOXPLAN_REGISTRY, MONGO_IMAGE y REDIS_IMAGE. El servidor de producción ya no tiene entonces ningún flujo hacia Internet para sus imágenes.

Paso 2: puesta en servicio​

Ambas versiones arrancan con --pull never: Docker Compose utiliza las imágenes presentes en la máquina y no contacta con ningún registro. Si falta alguna, la pila no arranca y se indica el nombre de la imagen ausente: repita el paso 1 con la lista correcta y vuelva a lanzar.

Sin esta opción, una imagen ausente se descargaría en silencio — o el arranque se quedaría bloqueado por un tiempo de espera, en un servidor sin acceso a Internet.

Prueba local​

En un puesto equipado con Docker Desktop — o con Docker y Docker Compose v2 — basta un solo archivo. El único valor que hay que proporcionar es la dirección del registro, comunicada por FoxPlan:

mkdir foxplan-essai && cd foxplan-essai
curl -O https://app.fox-plan.com/docs/onpremise/essai/docker-compose.yml
echo "FOXPLAN_REGISTRY=<dirección comunicada por FoxPlan>" > .env

# Comprueba que todas las imágenes están presentes y arranca sin descargar nada.
MISSING=$(docker compose config --images | while read -r i; do
docker image inspect "$i" >/dev/null 2>&1 || echo "$i"
done)
if [ -n "$MISSING" ]; then echo "Imágenes ausentes, no se ha arrancado nada:"; echo "$MISSING"
else docker compose up -d --pull never; fi

Sin FOXPLAN_REGISTRY, docker compose se detiene con un mensaje que la solicita. La comprobación previa lo detiene todo antes de crear el menor contenedor; --pull never por sí solo también se negaría a descargar, pero después de haber creado una parte de la pila.

El arranque tarda alrededor de un minuto. Después:

DirecciónContenido
http://localhost:8090FoxPlan — en el primer arranque, la página de diagnóstico y después la creación de su cuenta
http://localhost:8090/mailpit/Los emails enviados por FoxPlan, entre ellos el código de registro
http://localhost:8090/docs/Esta documentación

Ningún email sale del puesto: un buzón de prueba, Mailpit, los captura todos.

La prueba incluye un almacenamiento de archivos, Garage: los documentos de los proyectos, los archivos adjuntos de los contratos y las fotos de perfil funcionan sin configuración. En un Mac con Apple Silicon, los dos servicios de exportación se ejecutan con emulación amd64: es lo previsto, y algo más lento.

Mac con Apple Silicon: MongoDB debe ser arm64

Solo los dos servicios de exportación deben ejecutarse en amd64. Si las demás imágenes se descargaron o cargaron en amd64 —por ejemplo, desde un archivo preparado para un servidor amd64—, MongoDB no arranca: a partir de la versión 5 exige las instrucciones AVX, que la emulación no proporciona. El registro de mongo muestra entonces «MongoDB 5.0+ requires a CPU with AVX support», la aplicación falla con MongoTimeoutException y http://localhost:8090 responde 502.

Un docker pull sin opciones no corrige nada: la imagen amd64 ya presente se considera actualizada. Pida explícitamente la versión arm64 y vuelva a arrancar:

. ./.env # FOXPLAN_REGISTRY
for i in traefik:v3.6.4 mongo:8.2.7 redis:7.4 \
$FOXPLAN_REGISTRY/foxplan_prod:latest $FOXPLAN_REGISTRY/foxplan_prod-docs:latest; do
docker pull --platform linux/arm64 "$i"
done
docker compose up -d --pull never

Crear la primera cuenta​

La prueba no envía ningún correo real: el código de registro se lee en Mailpit.

  1. Abra http://localhost:8090: se abre una página de diagnóstico (pantalla Primera instalación) que le muestra si todo funciona correctamente, sin pedirle nada — consulte qué se comprueba.
  2. Si todo responde, haga clic en Crear la primera instancia con mi cuenta e introduzca una dirección de correo cuyo dominio exista — su dirección profesional, por ejemplo; un dominio inventado se rechaza. El mensaje se queda en Mailpit: nada sale del equipo.
  3. En otra pestaña, abra http://localhost:8090/mailpit/: el mensaje con el código de registro llega allí en unos segundos.
  4. Vuelva a FoxPlan e introduzca el código. El asistente le guía después por su perfil, su empresa, los módulos y las invitaciones, y por último le pide elegir su contraseña.

Esta primera cuenta es la administradora de la instancia. Las invitaciones y los restablecimientos de contraseña enviados después también llegan a Mailpit.

La prueba escucha en el puerto 8090 y no en el 80: macOS no permite a un usuario sin privilegios de administrador abrir el puerto 80 solo en la dirección 127.0.0.1, y Docker no muestra entonces ningún error: la página simplemente no responde.

¿El puerto 8090 ya está ocupado? Elija otro al arrancar:

FOXPLAN_PORT=8091 docker compose up -d --pull never

y abra después http://localhost:8091.

Para detenerlo todo: docker compose down (datos conservados), o docker compose down -v para empezar de cero.

Para actualizar la prueba, repita el paso 1 y vuelva a lanzar docker compose up -d --pull never. Un docker compose up -d sin opción descarga por sí mismo la última versión de las imágenes de FoxPlan: práctico en un puesto conectado, pero entonces el paso 2 deja de funcionar sin conexión.

Los datos de la prueba están vinculados al nombre de proyecto foxplan-essai, no a la carpeta: una carpeta nueva retoma la prueba existente, con sus cuentas. Para empezar de cero, use docker compose down -v.

Reservado a la evaluación

La prueba solo escucha en 127.0.0.1 — solo es accesible desde el propio puesto — y utiliza contraseñas conocidas por todos, escritas en el archivo. No la exponga en una red ni introduzca datos reales: para acoger a usuarios, pase a la instalación empresarial descrita a continuación.

Instalación empresarial​

Archivos para descargar:

  • docker-compose.yml — la pila completa. Este archivo no tiene que modificarse.
  • install.sh — prepara el despliegue: certificado, configuración de Traefik, controles de coherencia; comprueba las imágenes y después arranca la pila
  • env.example — las variables de infraestructura, que se guardan con el nombre .env
  • env_application.example — las variables de la aplicación, que se guardan con el nombre .env_application
  • env_mongo.example y env_redis.example — las credenciales de MongoDB y Redis, que se guardan con los nombres .env_mongo y .env_redis

Coloque los archivos en un mismo directorio. Toda la configuración se realiza en los archivos .env*: el docker-compose.yml permanece intacto, lo que hace que las actualizaciones de versión sean indoloras.

El resto de esta página se refiere a la instalación empresarial: arquitectura, requisitos previos, dimensionamiento y después el procedimiento de instalación propiamente dicho.

Arquitectura​

Internet o red interna
(DNS: DOMAIN → este servidor)
│ 80 / 443
▼
┌─────────┐ terminación TLS
│ Traefik │ redirección 80 → 443
└────┬────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
/ (app:5000) /docs (docs:8080) /gantt-export:3200
product-server docs gantt-export
│ │
├──► mongo (red interna) └──► gantt-msp (interno)
└──► redis (red interna)
ServicioFunciónImprescindible
product-serverAplicación FoxPlanSí
docsEsta documentación, servida bajo /docsNo
gantt-exportExportación de las planificaciones en PDF / PNG / Excel / iCalNo
gantt-mspImportación y exportación MS Project y PrimaveraNo
mongoBase de datosSí
redisCaché y sesiones compartidasSí
Almacenamiento S3Archivos: documentos, archivos adjuntos, fotos de perfil — el suyo o Garage, consulte Almacenamiento de archivosNo

Sin gantt-export, la aplicación funciona con normalidad: solo dejan de estar disponibles las exportaciones gráficas del Gantt. Sin almacenamiento S3, solo dejan de estarlo los archivos.

Requisitos previos​

  • Un servidor Linux con Docker y Docker Compose v2;
  • Un nombre de dominio cuyo registro DNS apunte a este servidor;
  • Los puertos 80 y 443 accesibles para sus usuarios — ningún otro, SSO incluido: vea Puertos y SSO;
  • Las imágenes, descargadas en el paso 1: desde el servidor, con un acceso saliente temporal al registro de FoxPlan y a Docker Hub, o importadas desde un entorno de transferencia;
  • Su licencia;
  • Para los archivos, un almacenamiento compatible con S3 — el suyo o Garage, consulte Almacenamiento de archivos;
  • Según el modo TLS elegido, un acceso saliente a la autoridad de certificación (ver más abajo).

Puertos y SSO​

El SSO empresarial no requiere ningún puerto adicional, ya se trate del inicio de sesión estándar de Microsoft o Google o de un SSO personalizado. El retorno de un inicio de sesión lo transporta el navegador del usuario hacia https://SU_DOMINIO/login/oauth2/code/…, por el mismo puerto 443 que el resto de la aplicación: el proveedor de identidad nunca abre una conexión hacia su servidor.

SentidoPuertoEntreFunción
Entrante443 (TCP)Puestos de los usuarios → TraefikLa aplicación y el retorno del SSO
Entrante80 (TCP)Puestos de los usuarios → TraefikRedirección a 443; desafío HTTP-01 en modo acme
Saliente443 (TCP)Contenedor product-server → proveedor de identidadIntercambio del token, claves de firma, perfil — dominios en la matriz de flujos

No publique el puerto 5000 de la aplicación: todo pasa por Traefik, que termina el TLS. Si su proveedor de identidad escucha en un puerto distinto de 443 — Keycloak en 8443, por ejemplo —, es ese puerto el que debe abrir en salida, del servidor hacia el proveedor.

SSO estándar: Microsoft y Google​

Se activa con las variables TENANTID_AZURE, CLIENT_ID_AZURE, GOOGLE_CLIENT_ID… de .env_application. La URL de redirección se construye a partir de BASE_URL:

https://SU_DOMINIO/login/oauth2/code/azure
https://SU_DOMINIO/login/oauth2/code/google

Declárela exactamente igual en la aplicación registrada en Microsoft Entra o Google. Microsoft y Google solo aceptan URL en https.

SSO personalizado: configurado en la aplicación​

Okta, Keycloak, Microsoft, Google o cualquier proveedor OIDC declarado en SSO de empresa. La URL de redirección se reconstruye entonces a partir de la petición recibida: Traefik transmite a la aplicación la dirección de origen — cabeceras X-Forwarded-Proto, X-Forwarded-Host y X-Forwarded-Port — y FoxPlan deduce de ella https://SU_DOMINIO/login/oauth2/code/<identificador>. El botón Verificar la configuración muestra la URL exacta que debe declarar.

Mantenga el puerto público 443

Traefik sustituye las cabeceras X-Forwarded-* por lo que ve él mismo. Si los usuarios acceden a FoxPlan por otro puerto público (:8443), o si un equipo situado delante de Traefik termina el TLS y le transmite HTTP, FoxPlan calcula una URL de redirección distinta de la del navegador: el proveedor rechaza el inicio de sesión (redirect_uri mismatch). Un balanceador situado delante de Traefik debe por tanto transmitirle el tráfico en HTTPS por el puerto 443, o en TCP sin descifrarlo, conservando el nombre de dominio de origen.

Dimensionamiento​

Los valores siguientes están algo por encima de lo que mide FoxPlan en funcionamiento, para conservar margen en los componentes principales.

Para la prueba local​

RecursoRecomendado
Procesador4 vCPU
Memoria asignada a Docker8 GB (6 GB como mínimo)
Disco15 GB libres (imágenes incluidas)

En reposo, la prueba consume unos 1,5 GB, de los cuales 1,1 GB para la aplicación. En Docker Desktop, la memoria asignada se ajusta en Settings → Resources. La prueba no fija ningún límite por contenedor: se adapta a la memoria asignada a Docker.

Para la instalación empresarial​

UsuariosProcesadorMemoriaDisco (SSD)
Hasta 1004 vCPU16 GB100 GB
De 100 a 5008 vCPU32 GB, con 2 instancias de la aplicación200 GB
Más alláConsulte Despliegue en Kubernetes

El disco incluye la base de datos, los archivos almacenados en Garage si está en el mismo servidor, y espacio para las copias de seguridad locales antes de su transferencia. Para colocar la base de datos en un disco dedicado, consulte Volúmenes y discos.

Límites de memoria de los contenedores​

El docker-compose.yml empresarial fija un límite de memoria por contenedor. Sin límite, la JVM de la aplicación y MongoDB se dimensionan cada uno sobre toda la memoria del servidor — una cuarta parte para la JVM, la mitad para la caché de MongoDB — y acaban disputándosela. Con un límite, cada uno se ajusta al suyo.

ContenedorLímite por defectoVariable (.env)
product-server (aplicación)6 GBFOXPLAN_MEM_APP
mongo4 GBFOXPLAN_MEM_MONGO
gantt-export2 GBFOXPLAN_MEM_EXPORT
redis1 GBFOXPLAN_MEM_REDIS
gantt-msp512 MBFOXPLAN_MEM_MSP
docs256 MBFOXPLAN_MEM_DOCS
traefik256 MBFOXPLAN_MEM_TRAEFIK

Es decir, 14 GB para un servidor de 16 GB: queda margen para el sistema operativo. En un servidor de 32 GB, duplique FOXPLAN_MEM_APP y FOXPLAN_MEM_MONGO. La aplicación dedica el 75 % de su límite a su heap de Java; MongoDB reserva automáticamente la mitad del suyo, menos 1 GB, para su caché.

El procesador no está limitado: el arranque de la aplicación y la generación de las exportaciones se benefician de ello, y un límite los ralentizaría sin proteger nada.

Varias instancias de la aplicación​

Para un gran número de usuarios, lance varias instancias de la aplicación en el mismo servidor. Traefik las detecta y reparte las peticiones entre ellas; las sesiones se comparten en Redis, por lo que un usuario puede pasar de una instancia a otra sin que se cierre su sesión. Las tareas programadas (informes, rutinas, sincronizaciones) toman un bloqueo en Redis: una sola instancia las ejecuta, sea cual sea su número.

En .env:

FOXPLAN_APP_REPLICAS=2

y después docker compose up -d. Cada instancia tiene su propio límite FOXPLAN_MEM_APP: la memoria necesaria aumenta en la misma proporción. Ejemplo para un servidor de 32 GB con 2 instancias:

FOXPLAN_APP_REPLICAS=2
FOXPLAN_MEM_APP=6g # por instancia: 12 GB en total
FOXPLAN_MEM_MONGO=8g

Es decir, unos 24 GB de límites en total con los demás contenedores: queda margen para el sistema.

¿Cuándo añadir una instancia? Cuando el procesador del contenedor de la aplicación permanece saturado en las horas punta (docker stats), en la práctica a partir de un centenar de usuarios activos al mismo tiempo. Una segunda instancia también mantiene el servicio disponible si una de ellas se reinicia.

Dos limitaciones, propias de un único servidor:

  • una actualización de versión recrea todas las instancias a la vez: la interrupción es breve, pero existe;
  • MongoDB sigue siendo un nodo único, en el mismo servidor.

Más allá de 500 usuarios, o para una alta disponibilidad real (varios servidores, despliegue progresivo sin interrupción), pase al Despliegue en Kubernetes.

Volúmenes y discos​

La base de datos MongoDB y los archivos almacenados en Garage se guardan en directorios del host, nunca en volúmenes Docker. Un volumen Docker desaparece con docker compose down -v o un docker system prune --volumes; un directorio del host, no. Ubicado en un disco dedicado, sobrevive además a una reinstalación de Docker o del sistema.

DatosEn el contenedorUbicación en el hostVariable (.env)
Base de datos MongoDB/data/dbDirectorio, obligatorioFOXPLAN_MONGO_DATA
Archivos en Garage (opcional)/var/lib/garage/meta, /var/lib/garage/dataDirectorio, subcarpetas meta y dataFOXPLAN_GARAGE_DATA
Redis (caché, sesiones)/dataVolumen Docker redis_data, o un directorioFOXPLAN_REDIS_DATA (opcional)
Certificados de Traefik/certs, /acmeCarpeta ./traefik/ de la instalación—

Sin FOXPLAN_MONGO_DATA, docker compose se niega a arrancar con un mensaje que la reclama. El env.example propone ./data/mongo, en la carpeta de instalación: es suficiente para un piloto. En producción, coloque la base de datos en un disco dedicado, como se describe a continuación. Redis puede quedarse en un volumen Docker: solo contiene la caché y las sesiones, que se reconstruyen solas (si se pierden, solo desconectan a los usuarios).

Montar un disco dedicado para la base de datos​

Los siguientes comandos se ejecutan en el servidor, como administrador, antes del primer arranque de FoxPlan. Suponen un disco adicional vacío, en SSD; un volumen de cabina de almacenamiento (SAN por iSCSI o Fibre Channel) también sirve. Evite los recursos compartidos de red NFS o SMB: MongoDB pierde en ellos rendimiento y fiabilidad.

1. Localizar el disco. Aparece sin partición ni punto de montaje, por ejemplo sdb:

lsblk -f

2. Formatearlo en XFS, el sistema de archivos recomendado por MongoDB (paquete xfsprogs). Este comando borra el disco: compruebe dos veces su nombre.

sudo mkfs.xfs -L foxplan-data /dev/sdb

3. Montarlo de forma permanente, por su identificador (UUID), que no cambia si el orden de los discos cambia al reiniciar:

sudo mkdir -p /srv/foxplan
UUID=$(sudo blkid -s UUID -o value /dev/sdb)
echo "UUID=$UUID /srv/foxplan xfs defaults,noatime 0 2" | sudo tee -a /etc/fstab
sudo mount -a
findmnt /srv/foxplan # debe mostrar /dev/sdb y xfs

4. Hacer que Docker espere a que el disco esté montado. Sin esta precaución, si el disco no está montado al arrancar el servidor, /srv/foxplan/mongo no es más que una carpeta vacía del disco del sistema: MongoDB arranca allí una base de datos vacía, y FoxPlan parece haberlo perdido todo.

sudo mkdir -p /etc/systemd/system/docker.service.d
printf '[Unit]\nRequiresMountsFor=/srv/foxplan\n' | sudo tee /etc/systemd/system/docker.service.d/foxplan-data.conf
sudo systemctl daemon-reload

Docker ya no arranca mientras /srv/foxplan no esté montado.

5. Indicar los directorios en .env:

FOXPLAN_MONGO_DATA=/srv/foxplan/mongo
FOXPLAN_GARAGE_DATA=/srv/foxplan/garage # solo si añade Garage

6. Preparar y arrancar. ./install.sh crea los directorios (con meta y data para Garage); la imagen de MongoDB ajusta ella misma sus permisos.

./install.sh
docker compose up -d

7. Comprobar que la base de datos escribe realmente en el disco dedicado:

docker inspect foxplan-mongo --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
# /srv/foxplan/mongo -> /data/db
sudo ls /srv/foxplan/mongo # archivos WiredTiger*, collection-*…
df -h /srv/foxplan # espacio libre del disco de datos

Vigile después el espacio libre: si MongoDB se queda sin espacio, se detiene en seco, y la aplicación con él.

Instalación existente: sacar la base de datos de un volumen Docker​

Las instalaciones anteriores guardaban la base de datos en el volumen Docker mongo_data. Tras actualizar el docker-compose.yml, indique FOXPLAN_MONGO_DATA y ejecute ./install.sh: si encuentra el directorio vacío mientras existe una base de datos en el volumen antiguo, se detiene y muestra el comando de copia que hay que ejecutar. En detalle:

docker compose stop product-server mongo
mkdir -p /srv/foxplan/mongo
docker run --rm -v SU_PROYECTO_mongo_data:/from:ro -v /srv/foxplan/mongo:/to alpine \
sh -c 'cp -a /from/. /to/'
./install.sh
docker compose up -d

El nombre exacto del volumen se obtiene con docker volume ls | grep mongo_data. Conserve el volumen antiguo hasta haber comprobado la aplicación y después elimínelo (docker volume rm). Mismo principio para Garage: copie garage_meta en $FOXPLAN_GARAGE_DATA/meta y garage_data en $FOXPLAN_GARAGE_DATA/data.

Instalación​

1. Preparación​

chmod +x install.sh
./install.sh

En la primera ejecución, el script crea .env a partir de env.example y se detiene para que pueda rellenarlo.

VariableDescripción
DOMAINDominio servido, por ejemplo foxplan.mi-empresa.es
FOXPLAN_REGISTRYDirección del registro de imágenes, comunicada por FoxPlan
FOXPLAN_IMAGENombre base de las imágenes — ya informado: foxplan_prod
FOXPLAN_VERSIONVersión desplegada — fije una versión concreta en producción
MONGO_IMAGE / REDIS_IMAGEVersiones validadas de las dependencias
FOXPLAN_MONGO_DATADirectorio de la base de datos en el host — preferiblemente un disco dedicado, consulte Volúmenes y discos
TLS_MODEselfsigned, acme o static — ver TLS
ACME_EMAIL, ACME_CA_SERVERAutoridad de certificación, si TLS_MODE=acme
latest en producción

FOXPLAN_VERSION=latest sigue la última versión publicada: práctico para un piloto, desaconsejado después. Un simple reinicio de contenedor puede entonces cambiar de versión sin que usted lo haya decidido. Fije una versión y realice las actualizaciones de forma explícita.

Vuelva a ejecutar después ./install.sh: genera el certificado por defecto y la configuración de Traefik correspondiente a su TLS_MODE. El script es idempotente — puede volver a ejecutarlo cuantas veces quiera, nunca sustituye un certificado ni un .env existentes.

2. Secretos de la aplicación​

Tres archivos, que se crean a partir de las plantillas descargadas más arriba:

cp env_application.example .env_application # base de datos, emails, S3, JWT…
cp env_mongo.example .env_mongo # credenciales de MongoDB
cp env_redis.example .env_redis # contraseña de Redis

SECRET_KEY_JWT se genera una vez para siempre:

openssl rand -base64 64 | tr -d '\n'

Coherencias que respetar, so pena de fallo en el arranque:

  • URL_MONGO retoma el usuario y la contraseña de .env_mongo, con el host mongo;
  • SPRING_REDIS_PASSWORD es idéntica a REDIS_PASSWORD;
  • BASE_URL vale https:// seguido de DOMAIN — es la que construye los enlaces contenidos en los emails.
Dos archivos, dos funciones

.env lo lee Docker Compose para ensamblar la pila; no se transmite al contenedor de la aplicación. Las variables de la aplicación — base de datos, emails, S3, JWT — van en .env_application. Una variable de la aplicación colocada en .env no tiene ningún efecto, sin aviso alguno.

Envío de emails​

FoxPlan envía invitaciones, restablecimientos de contraseña y notificaciones. Sin configuración, no sale ningún email: la aplicación arranca y funciona, pero estas funciones permanecen mudas.

A rellenar en .env_application:

MAIL_PROXY_TYPE=smtp
MAIL_FROM=foxplan@mon-entreprise.fr

MAIL_SMTP_HOST=smtp.mon-entreprise.fr
MAIL_SMTP_PORT=587
MAIL_SMTP_USERNAME=
MAIL_SMTP_PASSWORD=
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS=true
Comprobar los envíos desde la aplicación

La pestaña Configuración → Empresa → Emails muestra, email por email, lo que ha respondido su servidor de envío, con un banner verde / naranja / rojo sobre el estado de la plataforma. Es el primer lugar que conviene consultar cuando un usuario dice no haber recibido nada — consulte Registro de emails.

MAIL_PROXY_TYPE acepta también sendgrid o gmail, para una cuenta proporcionada por usted; MAIL_PROXY_PARAMS contiene entonces <remitente>;<clave o contraseña>. En la gran mayoría de las instalaciones, smtp es la opción adecuada.

Dos puntos deciden el destino de sus mensajes:

  • MAIL_FROM debe estar autorizado a enviar para su dominio (SPF, DKIM). Un remitente no alineado acaba en el correo no deseado, o es rechazado sin que la aplicación lo sepa.
  • BASE_URL construye los enlaces de los emails. Si falta o es incorrecta, los mensajes se envían correctamente pero los enlaces que contienen — invitación, restablecimiento — no llevan a ninguna parte.

Para comprobarlo tras el arranque, provoque un restablecimiento de contraseña desde la pantalla de inicio de sesión y siga los logs:

docker compose logs -f product-server | grep -i mail

Un fallo de envío aparece en ellos como advertencia: la aplicación nunca se interrumpe por un email no enviado.

3. Puesta en marcha​

./install.sh --start

El script comprueba primero que todas las imágenes de la pila están presentes en el servidor. Si falta alguna, indica su nombre y se detiene sin arrancar nada: descárguela (paso 1) y vuelva a lanzarlo. Si no, arranca la pila con docker compose up -d --pull never, sin contactar con ningún registro.

Verificación:

docker compose ps
curl -I https://SU_DOMINIO/
curl -I https://SU_DOMINIO/docs/
Comprobar la instalación desde la aplicación

Una vez conectado como administrador, la pestaña Configuración → Cuenta FoxPlan → Diagnóstico prueba la base de datos, Redis, el envío de correos (se le envía un mensaje de prueba), el almacenamiento S3 y el servicio de exportación — consulte Cuenta FoxPlan.

4. Crear la primera cuenta​

Abra https://SU_DOMINIO: mientras no exista ninguna instancia, se abre una página de diagnóstico (pantalla Primera instalación) que le muestra si todo funciona correctamente — consulte qué se comprueba. Si todo responde, haga clic en Crear la primera instancia con mi cuenta. FoxPlan envía un código de registro a la dirección introducida, a través del servidor SMTP configurado en .env_application: llega al buzón real del destinatario. Introduzca el código, siga el asistente (perfil, empresa, módulos, invitaciones) y elija después su contraseña.

Esta primera cuenta es la administradora de la instancia: utilice una dirección del equipo que explotará FoxPlan, no una cuenta personal provisional.

Configure el envío de correos antes de crear la cuenta

Sin un servidor SMTP operativo, el código de registro no se envía y no se puede crear ninguna cuenta. Por eso la pantalla de primera instalación solo propone la creación una vez alcanzado el servidor de envío. Revise la sección Envío de emails y siga los registros durante el alta: docker compose logs -f product-server | grep -i mail.

5. Verificar la instalación: diagnóstico​

El diagnóstico comprueba, tanto para la prueba como para la instalación empresarial:

ElementoComprobación
AplicaciónEl servidor de FoxPlan responde
Base de datosMongoDB responde
CachéRedis responde
Servidor de envío de emailsEl servidor SMTP es accesible (no se envía ningún email)
Almacenamiento de archivos adjuntosEl bucket S3 es accesible — «no configurado» si no hay ninguno
Exportación de las planificacionesEl servicio gantt-export responde, probado desde su navegador
DocumentaciónEl servicio docs responde, probado desde su navegador

Se abre de dos maneras:

  • En el primer arranque, mientras no se haya creado ninguna instancia: la pantalla Primera instalación lo lanza por sí sola y después propone crear la primera instancia si ningún elemento está en error. Un elemento «no configurado», como el almacenamiento en la prueba, no bloquea.
  • Después, en cualquier momento: el icono de diagnóstico, a la derecha del selector de idioma en la página de inicio de sesión. Solo aparece en una instalación on-premise y funciona sin haber iniciado sesión — precisamente cuando nadie consigue conectarse.

Sin iniciar sesión, el diagnóstico solo muestra estados, sin nombre de servidor ni mensaje técnico: la página de inicio de sesión es visible para cualquiera que llegue a ella. El detalle de cada error, y un email de prueba real, están en el diagnóstico completo, una vez conectado: Configuración → Cuenta FoxPlan → Diagnóstico — consulte Cuenta FoxPlan.

Almacenamiento de archivos​

FoxPlan guarda sus archivos en un almacenamiento de objetos compatible con S3: documentos de los proyectos, archivos adjuntos de los contratos de clientes, fotos de perfil. Los archivos permanecen privados: la aplicación los lee y los sirve ella misma, tras comprobar los derechos. Por tanto, el almacenamiento no tiene que ser accesible desde Internet ni gestionar permisos públicos.

Variable (.env_application)Función
FILE_S3_URLDirección del servicio S3, por ejemplo http://garage:3900
FILE_S3_BUCKETBucket de los archivos
FILE_S3_ACCESS_KEY, FILE_S3_SECRET_KEYCredenciales de acceso al bucket
FILE_S3_REGIONRegión firmada en cada solicitud — la que espera su almacenamiento. Por defecto: fr-par

Sin almacenamiento, deje estas variables vacías: la aplicación funciona, solo los archivos no están disponibles. Un archivo no puede superar 10 MB. FoxPlan no aplica ninguna cuota on-premise: el espacio es suyo.

Su almacenamiento existente​

Cualquier almacenamiento compatible con S3 sirve: Ceph, NetApp StorageGRID, Scality, MinIO, AWS… Cree un bucket y unas credenciales dedicados a FoxPlan, con derechos de lectura, escritura y eliminación sobre ese bucket, y después rellene las variables anteriores.

Ya dispone de un almacenamiento S3: no modifique el docker-compose.yml

La instalación empresarial no incluye Garage: se conecta al almacenamiento que usted indique. Deje el docker-compose.yml tal cual y no cree un docker-compose.override.yml; toda la configuración va en .env_application:

# .env_application — su almacenamiento S3
FILE_S3_URL=https://s3.mi-empresa.local # dirección del servicio S3, sin el nombre del bucket
FILE_S3_BUCKET=foxplan # bucket creado para FoxPlan
FILE_S3_ACCESS_KEY=<identificador de acceso>
FILE_S3_SECRET_KEY=<clave secreta>
FILE_S3_REGION=<región que espera el almacenamiento> # p. ej. us-east-1, eu-west-3, o la de su Ceph/MinIO

Después aplique la configuración: docker compose up -d product-server (el contenedor se vuelve a crear con los nuevos valores).

  • El servidor Docker debe alcanzar la dirección FILE_S3_URL (DNS, cortafuegos): es la aplicación la que lee y escribe los archivos, nunca el navegador.
  • En https, el certificado del almacenamiento debe estar firmado por una autoridad reconocida; un certificado interno no reconocido hace fallar la conexión.
  • La aplicación direcciona el bucket en la ruta (https://s3.mi-empresa.local/foxplan/…): no se necesita un DNS por bucket.
  • Compruebe el resultado en el diagnóstico: la línea Almacenamiento debe estar en verde.
La región debe ser la del almacenamiento

La región forma parte de la firma de cada solicitud. Si FILE_S3_REGION difiere de la que espera el almacenamiento, todas las solicitudes se rechazan (error 400 en los registros). El diagnóstico lo señala: almacenamiento en error.

Garage, si no tiene uno​

Garage es un almacenamiento S3 ligero y de código abierto, desarrollado en Francia. La prueba local ya lo incluye. Para añadirlo a la instalación empresarial sin tocar el docker-compose.yml, cree a su lado un archivo docker-compose.override.yml, que Docker Compose lee automáticamente:

services:
garage:
image: dxflrs/garage:v2.1.0
restart: always
volumes:
- ./garage.toml:/etc/garage.toml:ro
# Directorios del host (FOXPLAN_GARAGE_DATA en .env): sobreviven a «docker compose down -v».
- ${FOXPLAN_GARAGE_DATA:?Indique FOXPLAN_GARAGE_DATA en .env}/meta:/var/lib/garage/meta
- ${FOXPLAN_GARAGE_DATA:?Indique FOXPLAN_GARAGE_DATA en .env}/data:/var/lib/garage/data
networks:
- foxplan-network
deploy:
resources:
limits:
memory: 1g

Indique en .env el directorio de los archivos, preferiblemente en el disco de datos (consulte Volúmenes y discos), y vuelva a ejecutar ./install.sh, que crea sus subcarpetas meta y data:

FOXPLAN_GARAGE_DATA=/srv/foxplan/garage

Cree después el archivo garage.toml, con dos secretos generados para su instalación:

RPC_SECRET=$(openssl rand -hex 32)
ADMIN_TOKEN=$(openssl rand -base64 32)
printf '%s\n' \
'metadata_dir = "/var/lib/garage/meta"' \
'data_dir = "/var/lib/garage/data"' \
'db_engine = "sqlite"' \
'replication_factor = 1' \
'rpc_bind_addr = "[::]:3901"' \
'rpc_public_addr = "127.0.0.1:3901"' \
"rpc_secret = \"$RPC_SECRET\"" \
'' \
'[s3_api]' \
's3_region = "garage"' \
'api_bind_addr = "[::]:3900"' \
'' \
'[admin]' \
'api_bind_addr = "[::]:3903"' \
"admin_token = \"$ADMIN_TOKEN\"" > garage.toml
chmod 600 garage.toml

Arranque Garage y prepárelo — topología, bucket y clave de acceso:

docker compose up -d garage
docker compose exec garage /garage status # anote el ID del nodo (primera columna)
docker compose exec garage /garage layout assign -z dc1 -c 100G <ID_DEL_NODO>
docker compose exec garage /garage layout apply --version 1
docker compose exec garage /garage bucket create foxplan
docker compose exec garage /garage key create foxplan # muestra «Key ID» y «Secret key»
docker compose exec garage /garage bucket allow --read --write --owner foxplan --key foxplan

-c 100G es la capacidad anunciada del nodo: ajústela al espacio en disco reservado para los archivos. Por último, copie la clave en .env_application:

FILE_S3_URL=http://garage:3900
FILE_S3_BUCKET=foxplan
FILE_S3_ACCESS_KEY=<Key ID>
FILE_S3_SECRET_KEY=<Secret key>
FILE_S3_REGION=garage

y reinicie la aplicación:

docker compose up -d product-server

El diagnóstico (icono de la página de inicio de sesión, o Cuenta FoxPlan → Diagnóstico) debe mostrar el almacenamiento en OK. Garage solo es accesible desde la red interna del compose: no se publica ningún puerto.

Un valor formado por cifras va entre comillas

En un archivo YAML, un valor compuesto únicamente por cifras se lee como un número: una clave 0000…01 se convertiría en 1. Si coloca una clave en un archivo docker-compose, póngala entre comillas. En .env_application, el problema no se plantea.

Certificados entrantes y salientes​

FoxPlan utiliza dos certificados distintos, uno por sentido de circulación. No van ni al mismo sitio ni al mismo componente.

Flujos ENTRANTES navegador ──HTTPS──► Traefik ──► aplicación
Traefik presenta el certificado DE FOXPLAN → traefik/certs/

Flujos SALIENTES aplicación ──HTTPS, SMTP TLS──► AD FS, SMTP, S3 internos…
la aplicación verifica SU certificado gracias a → ca-sortant/
la autoridad que los firmó
Flujos entrantesFlujos salientes
FunciónDemostrar a los navegadores que el servidor es realmente DOMAINPermitir que la aplicación reconozca sus servicios internos
Usado porTraefikLa aplicación (Java)
ContenidoEl certificado del servidor FoxPlan y su clave privadaEl certificado público de la autoridad — raíz e intermedios si los hay. Nunca una clave privada
Ubicacióntraefik/certs/tls.crt y traefik/certs/tls.keyca-sortant/<nombre>.crt — uno o varios archivos; otro directorio posible con FOXPLAN_OUTBOUND_CA_DIR en .env
FormatoPEM. tls.crt: certificado del servidor seguido de sus intermedios. tls.key: clave sin cifrar, sin frase de contraseñaPEM, extensión .crt obligatoria. Un archivo puede contener varios certificados
ObligatorioSí — install.sh genera uno autofirmado mientras llega el suyoSolo si un servicio llamado está firmado por una autoridad no pública
AplicaciónEn caliente, tras touch traefik/dynamic/tls.yml — vea el modo staticAl arrancar: ./install.sh (controla los archivos), luego docker compose restart product-server

Un archivo PEM es un archivo de texto que solo contiene bloques -----BEGIN CERTIFICATE----- … -----END CERTIFICATE----- (y, para la clave, -----BEGIN PRIVATE KEY-----).

¿Es el mismo certificado?​

No. El certificado entrante es el de FoxPlan, acompañado de su clave privada. El certificado saliente es el de la autoridad que firmó los certificados de AD FS, del SMTP o del S3: público, sin clave.

En cambio, a menudo proceden de la misma PKI. Si la autoridad interna firmó a la vez el certificado de FoxPlan y el de AD FS, el certificado de su autoridad raíz sirve:

  • en ca-sortant/, para que la aplicación reconozca AD FS, el SMTP…;
  • en traefik/ca/, solo en modo acme con una PKI interna, para que Traefik reconozca el servidor ACME.

No copie nunca tls.crt ni tls.key en ca-sortant/: install.sh rechaza todo archivo que contenga una clave privada.

¿Hace falta una autoridad saliente?​

Desde el servidor FoxPlan, muestre el emisor del certificado de cada servicio interno:

# AD FS, S3, API: HTTPS
echo | openssl s_client -connect adfs.mi-empresa.local:443 -servername adfs.mi-empresa.local 2>/dev/null \
| openssl x509 -noout -issuer
# SMTP con STARTTLS
echo | openssl s_client -connect smtp.mi-empresa.local:587 -starttls smtp 2>/dev/null \
| openssl x509 -noout -issuer

Emisor público (DigiCert, Sectigo, GlobalSign, Let's Encrypt…): nada que hacer. Emisor interno: pida al equipo PKI el certificado de la autoridad raíz y de sus intermedios, en formato PEM, y deposítelo en ca-sortant/.

Si falta, el inicio de sesión SSO falla al volver del proveedor, o los emails no salen, con PKIX path building failed en docker compose logs product-server.

Convertir al formato PEM​

Archivo recibidoComando
.cer o .der binarioopenssl x509 -inform DER -in autoridad.cer -out ca-sortant/autoridad.crt
.p7b (cadena de autoridades)openssl pkcs7 -print_certs -in cadena.p7b | sed -n '/-----BEGIN/,/-----END/p' > ca-sortant/autoridad.crt
.pfx o .p12 (certificado de FoxPlan con su clave)openssl pkcs12 -in foxplan.pfx -nokeys | sed -n '/-----BEGIN/,/-----END/p' > traefik/certs/tls.crt
openssl pkcs12 -in foxplan.pfx -nocerts -nodes | sed -n '/-----BEGIN/,/-----END/p' > traefik/certs/tls.key

El sed elimina las líneas descriptivas (Bag Attributes, subject=…) que añade openssl. En ca-sortant/, impedirían el arranque de la aplicación: install.sh las detecta y se detiene indicando el comando de limpieza. Con un .pfx, compruebe que tls.crt empieza por el certificado del servidor, antes de sus intermedios.

TLS: tres modos​

Sea cual sea el modo, install.sh instala un certificado por defecto. Traefik lo sirve desde el primer arranque, y sigue sirviéndolo si la obtención automática falla: el sitio nunca cae por falta de certificado; en el peor de los casos, se degrada a una advertencia del navegador.

La elección se hace con la variable TLS_MODE, y depende de una sola pregunta: ¿quién emite sus certificados, y puede Traefik comunicarse con él?

TLS_MODEAutoridadAcceso salienteRenovaciónUso
selfsignedNingunaNinguno—Evaluación, piloto
acmeLet's Encrypt o su PKIHacia la autoridadAutomáticaProducción
staticCualquieraNingunoA su cargoRed cerrada, wildcard corporativo

selfsigned — arrancar de inmediato​

Modo por defecto. install.sh genera un certificado autofirmado para su DOMAIN, válido 825 días. La aplicación es accesible en HTTPS enseguida, sin DNS público ni apertura del cortafuegos.

Los navegadores mostrarán una advertencia: es lo esperado. Este modo sirve para validar la instalación, no para exponer el servicio a los usuarios.

acme — renovación automática​

TLS_MODE=acme
ACME_EMAIL=admin@mon-entreprise.fr
ACME_CA_SERVER=https://acme-v02.api.letsencrypt.org/directory

Traefik obtiene el certificado mediante un desafío HTTP-01 y lo renueva solo. Con Let's Encrypt, hay dos condiciones: el dominio debe resolver públicamente hacia este servidor, y el puerto 80 debe ser accesible desde Internet — el desafío pasa por él, aunque todo el tráfico se redirija después a 443.

Con su PKI interna, solo cambia la dirección. Si su autoridad expone un servicio ACME — step-ca, HashiCorp Vault, EJBCA — el protocolo es el mismo (RFC 8555):

ACME_CA_SERVER=https://ca.interne.mon-entreprise.fr/acme/acme/directory

Si esta autoridad es privada, Traefik no la conoce y la negociación fallaría por un certificado desconocido. Proporciónele la cadena de confianza:

cp /ruta/a/ca-interne.crt traefik/ca/ca-interne.crt

y después, en .env:

LEGO_CA_CERTIFICATES=/ca/ca-interne.crt

install.sh comprueba la presencia del archivo y le avisa si falta.

Es el modo preferente en una red cerrada: conserva la renovación automática sin ninguna dependencia de Internet, con la única condición de que el servidor alcance su autoridad.

Certificados de corta duración

Las PKI internas emiten a menudo certificados válidos durante pocos días. No tiene consecuencias: Traefik simplemente renueva con más frecuencia.

static — su certificado, su renovación​

Para un certificado obtenido fuera de línea — wildcard corporativo, certificado comercial, autoridad interna sin servicio ACME. No se configura ningún ACME: Traefik nunca sale a la red para obtener un certificado.

Sobrescriba simplemente los archivos generados con los suyos:

cp su-certificado.crt traefik/certs/tls.crt # cadena completa
cp su-clave.key traefik/certs/tls.key
chmod 600 traefik/certs/tls.key
Cadena completa

tls.crt debe contener el certificado del servidor seguido de sus intermedios, en este orden. Un certificado servido sin su cadena funciona en un navegador de escritorio — que a menudo la completa por sí solo — pero falla en móvil, en línea de comandos y en las llamadas a la API. Síntoma clásico y desconcertante: «en mi equipo funciona».

La renovación corre a su cargo. Traefik no vigila los archivos de certificado, solo su configuración en traefik/dynamic/: tras sustituir los dos archivos, toque esa configuración para que los vuelva a leer en caliente, sin reinicio ni corte.

touch traefik/dynamic/tls.yml

De lo contrario, tras la sustitución Traefik sigue sirviendo el certificado anterior hasta su próximo reinicio — o hasta que caduque. Si su PKI deposita los certificados renovados en un directorio, la tarea que los copia en traefik/certs/ debe terminar con este touch.

Cambiar de modo​

Modifique TLS_MODE en .env, vuelva a ejecutar ./install.sh y después:

docker compose up -d traefik

El certificado existente se conserva: para partir de un certificado nuevo, elimine traefik/certs/ antes de volver a ejecutar el script.

Verificar el certificado servido​

echo | openssl s_client -connect SU_DOMINIO:443 -servername SU_DOMINIO 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates

Si el emisor es su propio dominio cuando esperaba una autoridad, se está sirviendo el certificado por defecto: la obtención ha fallado. Por orden: el DNS (dig +short SU_DOMINIO), la apertura de los puertos y después docker compose logs traefik | grep -iE 'acme|certificate|error'.

Explotación habitual​

docker compose ps # estado de los servicios
docker compose logs -f product-server # logs de la aplicación
./install.sh --start # actualización de versión, tras el paso 1
docker compose down # parada, datos conservados

La base de datos MongoDB y los archivos de Garage residen en directorios del host (FOXPLAN_MONGO_DATA, FOXPLAN_GARAGE_DATA, consulte Volúmenes y discos): sobreviven a las paradas, a las actualizaciones de versión y a docker compose down -v. Una actualización de versión también se hace en dos pasos: descargue las imágenes de la nueva versión (paso 1), cambie FOXPLAN_VERSION en .env y vuelva a ejecutar ./install.sh --start.

Copias de seguridad

Un directorio de datos no es una copia de seguridad: no protege ni de una avería del disco ni de un borrado por error. Consulte Copia de seguridad y restauración.

Copia de seguridad y restauración​

Qué hay que guardar​

ElementoGuardarPor qué
MongoDBSíTodos los datos de negocio: proyectos, tareas, presupuestos, usuarios
Archivos .env*SíContienen claves sin las cuales los datos restaurados son inutilizables
RedisNoCaché y sesiones: se reconstruye solo, a costa de que los usuarios vuelvan a conectarse
ArchivosSí, al mismo tiempo que la base de datosDocumentos, archivos adjuntos, fotos de perfil: en el almacenamiento S3 (FILE_S3_*), no en MongoDB. Con Garage, consulte Archivos almacenados en Garage
Guarde .env_application junto con la base de datos

SECRET_KEY_JWT sirve también, por defecto, como clave de cifrado de los secretos SSO almacenados en base de datos. Restaurar un volcado de MongoDB sin esta clave hace que las configuraciones SSO sean indescifrables: están ahí, ilegibles, y hay que volver a introducirlas por completo. Una copia de la base sin los secretos no es una copia de seguridad completa.

Copia de seguridad​

El comando se ejecuta en el contenedor de MongoDB, que incluye las herramientas necesarias:

docker compose exec -T mongo sh -c \
'mongodump --username "$MONGO_INITDB_ROOT_USERNAME" \
--password "$MONGO_INITDB_ROOT_PASSWORD" \
--authenticationDatabase admin --archive --gzip' \
> foxplan-$(date +%F-%H%M).archive.gz

Tres detalles que cuentan:

  • Las credenciales se leen del entorno del contenedor, nunca se escriben en la línea de comandos — por tanto no aparecen ni en ps ni en el historial del shell. Una contraseña con espacios o caracteres especiales pasa así sin necesidad de escaparla.
  • --archive produce un solo archivo en lugar de una estructura de directorios: más sencillo de transferir, cifrar y verificar.
  • -T desactiva la asignación de un pseudoterminal; de lo contrario, el flujo binario se corrompería en la redirección.

La copia se realiza en caliente, sin detener la aplicación.

Restauración​

docker compose exec -T mongo sh -c \
'mongorestore --username "$MONGO_INITDB_ROOT_USERNAME" \
--password "$MONGO_INITDB_ROOT_PASSWORD" \
--authenticationDatabase admin --archive --gzip --drop' \
< foxplan-2026-09-15-0300.archive.gz

--drop sustituye cada colección por la versión guardada. Sin esta opción, la restauración se fusionaría con los datos presentes, lo que da un estado incoherente — rara vez es lo que se busca.

Vaciar la caché de Redis tras cualquier restauración

Redis conserva objetos que hacen referencia a los identificadores de la base antigua. Tras una restauración, esas referencias ya no corresponden a nada y la aplicación devuelve errores 500 desconcertantes — típicamente un objeto «no encontrado» aunque esté presente en la base.

docker compose exec redis sh -c 'redis-cli -a "$REDIS_PASSWORD" FLUSHALL'
docker compose restart product-server

Es el olvido más frecuente tras una restauración.

Archivos almacenados en Garage​

MongoDB hace referencia a los archivos por su clave: guarde la base de datos y los archivos en el mismo momento, para poder restaurarlos juntos.

En caliente, archivo por archivo — el método preferente. rclone copia el contenido del bucket en un directorio sin detener Garage; las ejecuciones siguientes solo transfieren lo que ha cambiado. Con las credenciales de .env_application:

NET=$(docker network ls --format '{{.Name}}' | grep foxplan-network)
KEY=$(grep ^FILE_S3_ACCESS_KEY= .env_application | cut -d= -f2)
SECRET=$(grep ^FILE_S3_SECRET_KEY= .env_application | cut -d= -f2)
mkdir -p /var/backups/foxplan/files
docker run --rm --network "$NET" -v /var/backups/foxplan/files:/backup \
rclone/rclone:1.68.2 sync :s3:foxplan /backup \
--s3-provider Other --s3-endpoint http://garage:3900 --s3-region garage \
--s3-access-key-id "$KEY" --s3-secret-access-key "$SECRET"

Para restaurar, invierta el origen y el destino (sync /backup :s3:foxplan), en un Garage preparado como en la instalación.

En frío, directorio completo — para una migración de servidor. Detenga Garage, archive su directorio (FOXPLAN_GARAGE_DATA, con sus subcarpetas meta y data) y vuelva a arrancarlo:

docker compose stop garage
sudo tar czf garage-$(date +%F).tar.gz -C /srv/foxplan/garage meta data
docker compose start garage

Las dos subcarpetas van juntas: unos datos sin sus metadatos son inutilizables. Como con MongoDB, transfiera las copias fuera del servidor.

Automatizar​

Una tarea cron diaria, con rotación de 30 días:

# /etc/cron.d/foxplan-backup
0 3 * * * root cd /opt/foxplan && \
docker compose exec -T mongo sh -c 'mongodump --username "$MONGO_INITDB_ROOT_USERNAME" --password "$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase admin --archive --gzip' \
> /var/backups/foxplan/foxplan-$(date +\%F).archive.gz 2>/var/log/foxplan-backup.log && \
find /var/backups/foxplan -name 'foxplan-*.archive.gz' -mtime +30 -delete

El % debe escaparse como \% en una tabla cron; de lo contrario, la línea se trunca en ese punto.

Una copia local no protege de nada

Un archivo guardado en el mismo servidor que respalda desaparece con él. Transfiéralo a un almacenamiento distinto — almacenamiento de objetos, servidor de copias, cinta. Es la mitad del trabajo, y es la mitad que se olvida.

Verificar que la copia es utilizable​

Una copia nunca restaurada no es una copia de seguridad: es una hipótesis. Pruébela periódicamente en un entorno separado — nunca en producción.

# Control rápido de la integridad del archivo, sin restaurar nada
gzip -t foxplan-2026-09-15-0300.archive.gz && echo "archivo íntegro"

Después, una o dos veces al año, realice una restauración completa en un servidor de pruebas y conéctese a la aplicación: es el único control que demuestra algo.

Alternativa: copia en frío de la base de datos​

Para una copia bit a bit en lugar de una exportación lógica, detenga la base de datos y archive su directorio (FOXPLAN_MONGO_DATA):

docker compose stop product-server mongo
sudo tar czf mongo-$(date +%F).tar.gz -C /srv/foxplan/mongo .
docker compose start mongo product-server

Más rápido con una base de datos grande, pero con dos contrapartidas: la aplicación queda interrumpida durante la operación, y el archivo solo puede restaurarse en una versión idéntica de MongoDB. mongodump sigue siendo el método preferente; reserve este para las migraciones de servidor.

Modo mantenimiento​

Una página de mantenimiento activable, que deja /docs accesible durante la intervención, se entrega con el paquete de despliegue acompañado. No está incluida en los archivos descargables anteriores. Contacte con el soporte para obtenerla.

Solución de problemas​

SíntomaPista
manifest unknown al arrancarFOXPLAN_VERSION inexistente
pull access deniedFOXPLAN_REGISTRY o FOXPLAN_IMAGE erróneo — la imagen se llama foxplan_prod — o un docker login antiguo rechazado por el registro: docker logout <host del registro>
required variable FOXPLAN_REGISTRY is missingFalta la dirección del registro en .env: indique el valor comunicado por FoxPlan
env file .env_mongo not foundNo se han creado los archivos de secretos — consulte Secretos de la aplicación
404 en /DOMAIN incorrecto, o aplicación no iniciada — consulte los logs
502 / 503La aplicación aún está arrancando, o se ha detenido por un error
Certificado no válidoDNS, puertos, logs ACME — consulte Verificar el certificado servido
Duda sobre una integraciónCuenta FoxPlan → Diagnóstico: base de datos, Redis, emails, S3 y exportación probados con un clic
No se recibe ningún emailFaltan MAIL_PROXY_TYPE o MAIL_SMTP_* — consulte Envío de emails
Emails recibidos pero enlaces no válidosBASE_URL ausente o distinta del dominio público
Emails en correo no deseadoMAIL_FROM no autorizado a enviar para el dominio (SPF/DKIM)
Error de autenticación de RedisSPRING_REDIS_PASSWORD distinta de REDIS_PASSWORD
Fallo de autenticación de MongoURL_MONGO incoherente con .env_mongo
Exportaciones Gantt fallidasServicio gantt-export detenido, o MSP_SERVICE_ENDPOINT inaccesible
Errores 500 «objeto no encontrado» tras una restauraciónCaché de Redis no vaciada — consulte Restauración
Configuración SSO ilegible tras una restauraciónSECRET_KEY_JWT distinta de la vigente en el momento del volcado
Almacenamiento en error en el diagnóstico, 400 en las solicitudes S3FILE_S3_REGION distinta de la región del almacenamiento (s3_region para Garage) — consulte Almacenamiento de archivos
Almacenamiento en error justo después de un cambio de clave de GarageLa clave de .env_application no es la de Garage: docker compose exec garage /garage key info foxplan --show-secret
«Archivo demasiado grande»Un archivo supera los 10 MB
«Cuota de almacenamiento alcanzada»Solo SaaS: la instancia ha agotado su espacio; una licencia puede ampliarlo
no matching manifest for linux/arm64Las imágenes de exportación solo se publican para amd64: mantenga platform: linux/amd64 en gantt-export y gantt-msp (Docker Desktop las emula)
La aplicación se reinicia en bucle, Client id of registration 'google' must not be empty en los registrosImagen anterior a octubre de 2026 y líneas de SSO de Microsoft o Google declaradas pero vacías en .env_application: actualice la imagen o comente esas líneas
FoxPlan vuelve a arrancar sin datos tras reiniciar el servidorDisco de datos no montado: MongoDB ha arrancado sobre la carpeta vacía del disco del sistema. findmnt /srv/foxplan, y después el paso 4 de Volúmenes y discos
required variable FOXPLAN_MONGO_DATA is missing a valueVariable ausente de .env: consulte Volúmenes y discos
MongoDB vacío tras indicar FOXPLAN_MONGO_DATALos datos se quedaron en el volumen Docker antiguo: consulte Instalación existente
La aplicación se reinicia en bucle, https://${FILE_S3_URL} en los registrosFaltan las variables FILE_S3_* en .env_application: declárelas, aunque estén vacías — los adjuntos no estarán disponibles
PKIX path building failed en los registros de la aplicaciónAutoridad interna de AD FS, del SMTP o del S3 ausente de ca-sortant/: vea Certificados entrantes y salientes