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 local | Instalación empresarial | |
|---|---|---|
| Para | Evaluar FoxPlan en un puesto de trabajo | Un servidor compartido, la producción |
| Archivos | un solo docker-compose.yml | docker-compose.yml, install.sh y archivos .env |
| Configuración | Ninguna | Dominio, TLS, SMTP, secretos |
| Acceso | http://localhost:8090, solo desde el puesto | https:// su dominio |
| Emails | Capturados por un buzón de prueba integrado | Enviados por su servidor SMTP |
Ambas versiones utilizan las mismas imágenes y se instalan en dos pasos distintos:
- 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;
- 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.
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.
| Imagen | Función |
|---|---|
foxplan_prod | Aplicación |
foxplan_prod-docs | Esta documentación |
foxplan_prod-export | Exportación de las planificaciones (PDF, PNG, Excel, iCal) |
foxplan_prod-msp | Importació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:
| Imagen | Prueba local | Instalación empresarial |
|---|---|---|
traefik:v3.6.4 | Sí | Sí |
mongo:8.2.7 | Sí | Sí (MONGO_IMAGE) |
redis:7.4 | Sí | Sí (REDIS_IMAGE) |
axllent/mailpit:v1.27 | Sí | No: sus correos salen por su servidor SMTP |
dxflrs/garage:v2.1.0 | Sí | Solo si añade Garage — consulte Almacenamiento de archivos |
curlimages/curl:8.14.1 | Sí: inicializa Garage en el primer arranque | No |
rclone/rclone:1.68.2 | No | Solo 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):
| Destino | Función | Imágenes afectadas |
|---|---|---|
| Host del registro de FoxPlan | Índice de las imágenes | foxplan_prod, -docs, -export, -msp |
| Servicio de tokens del registro de FoxPlan | Token de acceso anónimo | ídem |
| Almacenamiento de objetos del registro de FoxPlan | Capas de las imágenes: el registro redirige las descargas hacia este almacenamiento | ídem |
registry-1.docker.io, auth.docker.io | Docker Hub: índice de las imágenes y token de acceso | Imágenes de terceros |
production.cloudflare.docker.com, production.cloudfront.docker.com | Capas de las imágenes de Docker Hub (CDN) | ídem |
app.fox-plan.com | Descarga de los archivos de despliegue de esta página | Prueba 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
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ón | Contenido |
|---|---|
http://localhost:8090 | FoxPlan — 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.
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.
- 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. - 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.
- En otra pestaña, abra
http://localhost:8090/mailpit/: el mensaje con el código de registro llega allí en unos segundos. - 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.
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_mongoy.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)
| Servicio | Función | Imprescindible |
|---|---|---|
product-server | Aplicación FoxPlan | Sí |
docs | Esta documentación, servida bajo /docs | No |
gantt-export | Exportación de las planificaciones en PDF / PNG / Excel / iCal | No |
gantt-msp | Importación y exportación MS Project y Primavera | No |
mongo | Base de datos | Sí |
redis | Caché y sesiones compartidas | Sí |
| Almacenamiento S3 | Archivos: documentos, archivos adjuntos, fotos de perfil — el suyo o Garage, consulte Almacenamiento de archivos | No |
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.
| Sentido | Puerto | Entre | Función |
|---|---|---|---|
| Entrante | 443 (TCP) | Puestos de los usuarios → Traefik | La aplicación y el retorno del SSO |
| Entrante | 80 (TCP) | Puestos de los usuarios → Traefik | Redirección a 443; desafío HTTP-01 en modo acme |
| Saliente | 443 (TCP) | Contenedor product-server → proveedor de identidad | Intercambio 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.
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
| Recurso | Recomendado |
|---|---|
| Procesador | 4 vCPU |
| Memoria asignada a Docker | 8 GB (6 GB como mínimo) |
| Disco | 15 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
| Usuarios | Procesador | Memoria | Disco (SSD) |
|---|---|---|---|
| Hasta 100 | 4 vCPU | 16 GB | 100 GB |
| De 100 a 500 | 8 vCPU | 32 GB, con 2 instancias de la aplicación | 200 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.
| Contenedor | Límite por defecto | Variable (.env) |
|---|---|---|
product-server (aplicación) | 6 GB | FOXPLAN_MEM_APP |
mongo | 4 GB | FOXPLAN_MEM_MONGO |
gantt-export | 2 GB | FOXPLAN_MEM_EXPORT |
redis | 1 GB | FOXPLAN_MEM_REDIS |
gantt-msp | 512 MB | FOXPLAN_MEM_MSP |
docs | 256 MB | FOXPLAN_MEM_DOCS |
traefik | 256 MB | FOXPLAN_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.
| Datos | En el contenedor | Ubicación en el host | Variable (.env) |
|---|---|---|---|
| Base de datos MongoDB | /data/db | Directorio, obligatorio | FOXPLAN_MONGO_DATA |
| Archivos en Garage (opcional) | /var/lib/garage/meta, /var/lib/garage/data | Directorio, subcarpetas meta y data | FOXPLAN_GARAGE_DATA |
| Redis (caché, sesiones) | /data | Volumen Docker redis_data, o un directorio | FOXPLAN_REDIS_DATA (opcional) |
| Certificados de Traefik | /certs, /acme | Carpeta ./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.
| Variable | Descripción |
|---|---|
DOMAIN | Dominio servido, por ejemplo foxplan.mi-empresa.es |
FOXPLAN_REGISTRY | Dirección del registro de imágenes, comunicada por FoxPlan |
FOXPLAN_IMAGE | Nombre base de las imágenes — ya informado: foxplan_prod |
FOXPLAN_VERSION | Versión desplegada — fije una versión concreta en producción |
MONGO_IMAGE / REDIS_IMAGE | Versiones validadas de las dependencias |
FOXPLAN_MONGO_DATA | Directorio de la base de datos en el host — preferiblemente un disco dedicado, consulte Volúmenes y discos |
TLS_MODE | selfsigned, acme o static — ver TLS |
ACME_EMAIL, ACME_CA_SERVER | Autoridad de certificación, si TLS_MODE=acme |
latest en producciónFOXPLAN_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_MONGOretoma el usuario y la contraseña de.env_mongo, con el hostmongo;SPRING_REDIS_PASSWORDes idéntica aREDIS_PASSWORD;BASE_URLvalehttps://seguido deDOMAIN— es la que construye los enlaces contenidos en los emails.
.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
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_FROMdebe 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_URLconstruye 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/
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.
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:
| Elemento | Comprobación |
|---|---|
| Aplicación | El servidor de FoxPlan responde |
| Base de datos | MongoDB responde |
| Caché | Redis responde |
| Servidor de envío de emails | El servidor SMTP es accesible (no se envía ningún email) |
| Almacenamiento de archivos adjuntos | El bucket S3 es accesible — «no configurado» si no hay ninguno |
| Exportación de las planificaciones | El servicio gantt-export responde, probado desde su navegador |
| Documentación | El 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_URL | Dirección del servicio S3, por ejemplo http://garage:3900 |
FILE_S3_BUCKET | Bucket de los archivos |
FILE_S3_ACCESS_KEY, FILE_S3_SECRET_KEY | Credenciales de acceso al bucket |
FILE_S3_REGION | Regió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.
docker-compose.ymlLa 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 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.
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 entrantes | Flujos salientes | |
|---|---|---|
| Función | Demostrar a los navegadores que el servidor es realmente DOMAIN | Permitir que la aplicación reconozca sus servicios internos |
| Usado por | Traefik | La aplicación (Java) |
| Contenido | El certificado del servidor FoxPlan y su clave privada | El certificado público de la autoridad — raíz e intermedios si los hay. Nunca una clave privada |
| Ubicación | traefik/certs/tls.crt y traefik/certs/tls.key | ca-sortant/<nombre>.crt — uno o varios archivos; otro directorio posible con FOXPLAN_OUTBOUND_CA_DIR en .env |
| Formato | PEM. tls.crt: certificado del servidor seguido de sus intermedios. tls.key: clave sin cifrar, sin frase de contraseña | PEM, extensión .crt obligatoria. Un archivo puede contener varios certificados |
| Obligatorio | Sí — install.sh genera uno autofirmado mientras llega el suyo | Solo si un servicio llamado está firmado por una autoridad no pública |
| Aplicación | En caliente, tras touch traefik/dynamic/tls.yml — vea el modo static | Al 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 modoacmecon 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 recibido | Comando |
|---|---|
.cer o .der binario | openssl 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.crtopenssl 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_MODE | Autoridad | Acceso saliente | Renovación | Uso |
|---|---|---|---|---|
selfsigned | Ninguna | Ninguno | — | Evaluación, piloto |
acme | Let's Encrypt o su PKI | Hacia la autoridad | Automática | Producción |
static | Cualquiera | Ninguno | A su cargo | Red 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.
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
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.
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
| Elemento | Guardar | Por qué |
|---|---|---|
| MongoDB | Sí | Todos los datos de negocio: proyectos, tareas, presupuestos, usuarios |
Archivos .env* | Sí | Contienen claves sin las cuales los datos restaurados son inutilizables |
| Redis | No | Caché y sesiones: se reconstruye solo, a costa de que los usuarios vuelvan a conectarse |
| Archivos | Sí, al mismo tiempo que la base de datos | Documentos, archivos adjuntos, fotos de perfil: en el almacenamiento S3 (FILE_S3_*), no en MongoDB. Con Garage, consulte Archivos almacenados en Garage |
.env_application junto con la base de datosSECRET_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
psni en el historial del shell. Una contraseña con espacios o caracteres especiales pasa así sin necesidad de escaparla. --archiveproduce un solo archivo en lugar de una estructura de directorios: más sencillo de transferir, cifrar y verificar.-Tdesactiva 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.
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.
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íntoma | Pista |
|---|---|
manifest unknown al arrancar | FOXPLAN_VERSION inexistente |
pull access denied | FOXPLAN_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 missing | Falta la dirección del registro en .env: indique el valor comunicado por FoxPlan |
env file .env_mongo not found | No 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 / 503 | La aplicación aún está arrancando, o se ha detenido por un error |
| Certificado no válido | DNS, puertos, logs ACME — consulte Verificar el certificado servido |
| Duda sobre una integración | Cuenta FoxPlan → Diagnóstico: base de datos, Redis, emails, S3 y exportación probados con un clic |
| No se recibe ningún email | Faltan MAIL_PROXY_TYPE o MAIL_SMTP_* — consulte Envío de emails |
| Emails recibidos pero enlaces no válidos | BASE_URL ausente o distinta del dominio público |
| Emails en correo no deseado | MAIL_FROM no autorizado a enviar para el dominio (SPF/DKIM) |
| Error de autenticación de Redis | SPRING_REDIS_PASSWORD distinta de REDIS_PASSWORD |
| Fallo de autenticación de Mongo | URL_MONGO incoherente con .env_mongo |
| Exportaciones Gantt fallidas | Servicio gantt-export detenido, o MSP_SERVICE_ENDPOINT inaccesible |
| Errores 500 «objeto no encontrado» tras una restauración | Caché de Redis no vaciada — consulte Restauración |
| Configuración SSO ilegible tras una restauración | SECRET_KEY_JWT distinta de la vigente en el momento del volcado |
Almacenamiento en error en el diagnóstico, 400 en las solicitudes S3 | FILE_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 Garage | La 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/arm64 | Las 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 registros | Imagen 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 servidor | Disco 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 value | Variable ausente de .env: consulte Volúmenes y discos |
MongoDB vacío tras indicar FOXPLAN_MONGO_DATA | Los 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 registros | Faltan 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ón | Autoridad interna de AD FS, del SMTP o del S3 ausente de ca-sortant/: vea Certificados entrantes y salientes |