Aller au contenu principal

Déploiement Docker Compose

Cette page décrit le déploiement de FoxPlan sur un serveur unique avec Docker Compose, derrière Traefik qui assure le routage et la terminaison TLS.

Pour une cible de production avec montée en charge et haute disponibilité, voir Déploiement Kubernetes.

Deux versions​

Essai localInstallation entreprise
PourÉvaluer FoxPlan sur un posteUn serveur partagé, la production
Fichiersun seul docker-compose.ymldocker-compose.yml, install.sh et des fichiers .env
ConfigurationAucuneDomaine, TLS, SMTP, secrets
Accèshttp://localhost:8090, depuis le poste seulementhttps:// votre domaine
EmailsCaptés par une boîte de test intégréeEnvoyés par votre serveur SMTP

Les deux versions utilisent les mêmes images et s'installent en deux étapes distinctes :

  1. Télécharger les images : le seul moment où un accès à Internet est nécessaire, depuis le serveur lui-même ou depuis une machine de transfert ;
  2. Mise en place — essai local ou installation entreprise : la pile démarre uniquement sur les images déjà présentes. Aucun registre n'est contacté ; s'il manque une image, rien ne démarre.
Registre d'images FoxPlan

L'adresse du registre vous est communiquée par FoxPlan. Renseignez-la dans la variable FOXPLAN_REGISTRY, dans le fichier .env placé à côté du docker-compose.yml — pour l'essai comme pour l'installation entreprise. L'accès est en lecture libre : aucun identifiant ni docker login n'est nécessaire.

ImageRôle
foxplan_prodApplication
foxplan_prod-docsCette documentation
foxplan_prod-exportExport des plannings (PDF, PNG, Excel, iCal)
foxplan_prod-mspImport et export MS Project et Primavera

latest suit la dernière version publiée ; chaque version porte aussi un tag immuable, à épingler en production. Les images tierces (MongoDB, Redis, Traefik…) sont tirées depuis Docker Hub, pas depuis ce registre.

Étape 1 : télécharger les images​

Le téléchargement des images est le seul moment où la pile a besoin d'accéder à Internet. Une fois les images présentes sur le serveur, FoxPlan ne contacte plus aucun registre : ni au démarrage, ni en fonctionnement, ni au redémarrage d'un conteneur.

Images à télécharger​

Les quatre images FoxPlan ci-dessus, dans la version que vous déployez, et les images tierces suivantes, toutes tirées de Docker Hub :

ImageEssai localInstallation entreprise
traefik:v3.6.4OuiOui
mongo:8.2.7OuiOui (MONGO_IMAGE)
redis:7.4OuiOui (REDIS_IMAGE)
axllent/mailpit:v1.27OuiNon : vos emails partent par votre serveur SMTP
dxflrs/garage:v2.1.0OuiSeulement si vous ajoutez Garage — voir Stockage des fichiers
curlimages/curl:8.14.1Oui : initialise Garage au premier démarrageNon
rclone/rclone:1.68.2NonSeulement pour sauvegarder Garage

Avec votre propre stockage S3, ni Garage, ni curl, ni rclone ne sont nécessaires. Si vous changez MONGO_IMAGE ou REDIS_IMAGE dans le .env, téléchargez l'image que vous y indiquez.

Flux réseau​

Flux sortants émis par le moteur Docker pendant le téléchargement, tous en HTTPS (TCP/443) :

DestinationRôleImages concernées
Hôte du registre FoxPlanIndex des imagesfoxplan_prod, -docs, -export, -msp
Service de jetons du registre FoxPlanJeton d'accès anonymeidem
Stockage objet du registre FoxPlanCouches des images : le registre redirige les téléchargements vers ce stockageidem
registry-1.docker.io, auth.docker.ioDocker Hub : index des images et jeton d'accèsImages tierces
production.cloudflare.docker.com, production.cloudfront.docker.comCouches des images Docker Hub (CDN)idem
app.fox-plan.comTéléchargement des fichiers de déploiement de cette pageEssai local (curl)

Si votre pare-feu filtre par image (proxy de registre, liste blanche Harbor, Nexus, Artifactory…), autorisez les noms et versions ci-dessus.

Les trois hôtes du registre FoxPlan vous sont communiqués avec son adresse. Un pare-feu qui n'autorise que le domaine du registre laisse passer l'index mais bloque les couches : le pull reste alors figé ou échoue sur un délai d'attente. Les redirections vers le stockage objet et le CDN doivent être autorisées elles aussi.

Les flux de fonctionnement — autorité de certification ACME, serveur SMTP, stockage S3, fournisseurs SSO — sont décrits dans la matrice des flux réseau.

Depuis le serveur​

Si le serveur peut joindre les registres le temps du téléchargement :

FOXPLAN_REGISTRY=<adresse communiquée par FoxPlan>
V=latest # en production : la version épinglée dans 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
# Essai local : ajoutez la boîte de test et le stockage intégré.
# IMAGES+=(axllent/mailpit:v1.27 dxflrs/garage:v2.1.0 curlimages/curl:8.14.1)
# Installation entreprise avec 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

Les images foxplan_prod-export et foxplan_prod-msp n'existent qu'en amd64 : la boucle les tire toujours dans cette architecture. Sur un serveur ARM ou un Mac Apple Silicon, elles tournent en émulation. Une ligne ÉCHEC signale une image non téléchargée : corrigez avant de passer à l'étape 2.

Les accès vers les registres peuvent ensuite être refermés : l'étape 2 n'en a plus besoin.

Depuis un environnement indépendant​

Secteur sensible : récupérez les images hors de votre SI

Si votre organisation relève d'un secteur sensible — défense, santé, énergie, finance, administration, opérateur d'importance vitale — n'ouvrez pas ces flux depuis le serveur de production ni depuis votre réseau interne.

Récupérez les images sur une machine indépendante de votre SI (poste ou environnement de transfert dédié, sans accès à vos réseaux internes), contrôlez-les, puis faites-les entrer par votre circuit habituel : registre interne, ou archive transférée sur support maîtrisé.

# 1. Sur la machine de transfert, hors de votre SI.
# Composez IMAGES exactement comme ci-dessus (essai ou entreprise, Garage ou non) :
# une image oubliée ici manquera au démarrage.
# --platform : l'architecture de votre serveur cible (amd64 dans la plupart des cas).
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. Contrôles selon votre politique : antivirus, analyse de vulnérabilités
# (Trivy, Grype…), empreintes des images (docker images --digests).

# 3. Sur le serveur cible, sans accès à Internet.
sha256sum -c foxplan-images.tar.sha256
docker load -i foxplan-images.tar

Les images chargées portent leur nom d'origine : l'étape 2 les trouve sur place. Si vous passez par un registre interne, retaguez-les vers celui-ci (docker tag, docker push), tirez-les depuis le serveur, et renseignez ce registre dans FOXPLAN_REGISTRY, MONGO_IMAGE et REDIS_IMAGE. Le serveur de production n'a alors plus aucun flux vers Internet pour ses images.

Étape 2 : mise en place​

Les deux versions démarrent avec --pull never : Docker Compose utilise les images présentes sur la machine et ne contacte aucun registre. S'il en manque une, la pile ne démarre pas et l'image absente est nommée : refaites l'étape 1 avec la bonne liste, puis relancez.

Sans cette option, une image absente serait téléchargée en silence — ou le démarrage resterait bloqué sur un délai d'attente, sur un serveur sans accès à Internet.

Essai local​

Sur un poste équipé de Docker Desktop — ou de Docker et Docker Compose v2 — un seul fichier suffit. La seule valeur à fournir est l'adresse du registre, communiquée par FoxPlan :

mkdir foxplan-essai && cd foxplan-essai
curl -O https://app.fox-plan.com/docs/onpremise/essai/docker-compose.yml
echo "FOXPLAN_REGISTRY=<adresse communiquée par FoxPlan>" > .env

# Vérifie que toutes les images sont présentes, puis démarre sans rien télécharger.
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 "Images absentes, rien n'a démarré :"; echo "$MISSING"
else docker compose up -d --pull never; fi

Sans FOXPLAN_REGISTRY, docker compose s'arrête avec un message qui la réclame. La vérification préalable arrête tout avant la création du moindre conteneur ; --pull never seul refuserait aussi de télécharger, mais après avoir créé une partie de la pile.

Le démarrage prend environ une minute. Ensuite :

AdresseContenu
http://localhost:8090FoxPlan — au premier lancement, la page de diagnostic, puis la création de votre compte
http://localhost:8090/mailpit/Les emails envoyés par FoxPlan, dont le code d'inscription
http://localhost:8090/docs/Cette documentation

Aucun email ne sort du poste : une boîte de test, Mailpit, les capte tous.

L'essai embarque un stockage de fichiers, Garage : documents des projets, pièces jointes des contrats et photos de profil fonctionnent sans configuration. Sur un Mac Apple Silicon, les deux services d'export tournent en émulation amd64 : c'est prévu, et un peu plus lent.

Mac Apple Silicon : MongoDB doit être en arm64

Seuls les deux services d'export doivent tourner en amd64. Si les autres images ont été tirées ou chargées en amd64 — par exemple depuis une archive préparée pour un serveur amd64 —, MongoDB ne démarre pas : à partir de la version 5, il exige les instructions AVX, que l'émulation ne fournit pas. Le journal de mongo affiche alors « MongoDB 5.0+ requires a CPU with AVX support », l'application échoue sur MongoTimeoutException et http://localhost:8090 répond 502.

Un docker pull sans option ne corrige rien : l'image amd64 déjà présente est jugée à jour. Demandez explicitement la version arm64, puis relancez :

. ./.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

Créer le premier compte​

L'essai n'envoie aucun email réel : le code d'inscription se lit dans Mailpit.

  1. Ouvrez http://localhost:8090 : une page de diagnostic s'ouvre (écran Première installation) et vous montre si tout fonctionne correctement, sans rien vous demander — voir ce qui est vérifié.
  2. Si tout répond, cliquez sur Créer la première instance avec mon compte et saisissez une adresse email dont le domaine existe — votre adresse professionnelle, par exemple ; un domaine inventé est refusé. Le message reste dans Mailpit : rien ne sort du poste.
  3. Dans un autre onglet, ouvrez http://localhost:8090/mailpit/ : le message contenant le code d'inscription y arrive en quelques secondes.
  4. Revenez sur FoxPlan et saisissez ce code. L'assistant enchaîne ensuite votre profil, votre entreprise, les modules et les invitations, puis vous fait choisir votre mot de passe.

Ce premier compte est administrateur de l'instance. Les invitations et réinitialisations de mot de passe envoyées ensuite arrivent elles aussi dans Mailpit.

L'essai écoute sur le port 8090 et non 80 : macOS refuse à un utilisateur non administrateur d'ouvrir le port 80 sur la seule adresse 127.0.0.1, et Docker n'affiche alors aucune erreur — la page ne répond simplement pas.

Le port 8090 est déjà pris ? Choisissez-en un autre au lancement :

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

puis ouvrez http://localhost:8091.

Pour tout arrêter : docker compose down (données conservées), ou docker compose down -v pour repartir de zéro.

Pour mettre l'essai à jour, refaites l'étape 1, puis relancez docker compose up -d --pull never. Un docker compose up -d sans option télécharge lui-même la dernière version des images FoxPlan : pratique sur un poste connecté, mais l'étape 2 n'est alors plus hors ligne.

Les données de l'essai sont rattachées au nom de projet foxplan-essai, pas au dossier : un nouveau dossier reprend l'essai existant, avec ses comptes. Pour repartir de zéro, utilisez docker compose down -v.

Réservé à l'évaluation

L'essai n'écoute que sur 127.0.0.1 — il n'est joignable que depuis le poste lui-même — et utilise des mots de passe connus de tous, écrits dans le fichier. Ne l'exposez pas sur un réseau et n'y saisissez pas de données réelles : pour accueillir des utilisateurs, passez à l'installation entreprise ci-dessous.

Installation entreprise​

Fichiers à télécharger :

  • docker-compose.yml — la pile complète. Ce fichier n'a pas à être modifié.
  • install.sh — prépare le déploiement : certificat, configuration Traefik, contrôles de cohérence ; vérifie les images puis démarre la pile
  • env.example — les variables d'infrastructure, à enregistrer sous le nom .env
  • env_application.example — les variables de l'application, à enregistrer sous le nom .env_application
  • env_mongo.example et env_redis.example — les identifiants MongoDB et Redis, à enregistrer sous les noms .env_mongo et .env_redis

Placez les fichiers dans un même répertoire. Toute la configuration se fait dans les fichiers .env* : le docker-compose.yml reste intact, ce qui rend les montées de version indolores.

La suite de cette page concerne l'installation entreprise : architecture, prérequis, dimensionnement, puis la procédure d'installation proprement dite.

Architecture​

Internet ou réseau interne
(DNS : DOMAIN → ce serveur)
│ 80 / 443
▼
┌─────────┐ terminaison TLS
│ Traefik │ redirection 80 → 443
└────┬────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
/ (app:5000) /docs (docs:8080) /gantt-export:3200
product-server docs gantt-export
│ │
├──► mongo (réseau interne) └──► gantt-msp (interne)
└──► redis (réseau interne)
ServiceRôleIndispensable
product-serverApplication FoxPlanOui
docsCette documentation, servie sous /docsNon
gantt-exportExport des plannings en PDF / PNG / Excel / iCalNon
gantt-mspImport et export MS Project et PrimaveraNon
mongoBase de donnéesOui
redisCache et sessions partagéesOui
Stockage S3Fichiers : documents, pièces jointes, photos de profil — le vôtre ou Garage, voir Stockage des fichiersNon

Sans gantt-export, l'application fonctionne normalement : seuls les exports graphiques du Gantt sont indisponibles. Sans stockage S3, seuls les fichiers le sont.

Prérequis​

  • Un serveur Linux avec Docker et Docker Compose v2 ;
  • Un nom de domaine dont l'enregistrement DNS pointe vers ce serveur ;
  • Les ports 80 et 443 joignables par vos utilisateurs — aucun autre, SSO compris : voir Ports et SSO ;
  • Les images, téléchargées à l'étape 1 : depuis le serveur, avec un accès sortant temporaire vers le registre FoxPlan et Docker Hub, ou importées depuis un environnement de transfert ;
  • Votre licence ;
  • Pour les fichiers, un stockage compatible S3 — le vôtre ou Garage, voir Stockage des fichiers ;
  • Selon le mode TLS retenu, un accès sortant vers l'autorité de certification (voir plus bas).

Ports et SSO​

Le SSO d'entreprise n'exige aucun port supplémentaire, qu'il s'agisse de la connexion Microsoft ou Google standard ou d'un SSO personnalisé. Le retour d'une connexion est porté par le navigateur de l'utilisateur vers https://VOTRE_DOMAINE/login/oauth2/code/…, sur le même port 443 que le reste de l'application : le fournisseur d'identité n'ouvre jamais de connexion vers votre serveur.

SensPortEntreRôle
Entrant443 (TCP)Postes des utilisateurs → TraefikL'application et le retour du SSO
Entrant80 (TCP)Postes des utilisateurs → TraefikRedirection vers 443 ; challenge HTTP-01 en mode acme
Sortant443 (TCP)Conteneur product-server → fournisseur d'identitéÉchange du jeton, clés de signature, profil — domaines dans la matrice des flux

Ne publiez pas le port 5000 de l'application : tout passe par Traefik, qui termine le TLS. Si votre fournisseur d'identité écoute sur un autre port que 443 — Keycloak sur 8443, par exemple —, c'est ce port qu'il faut ouvrir en sortie, du serveur vers le fournisseur.

SSO standard : Microsoft et Google​

Activé par les variables TENANTID_AZURE, CLIENT_ID_AZURE, GOOGLE_CLIENT_ID… de .env_application. L'URL de redirection est construite à partir de BASE_URL :

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

Déclarez-la à l'identique dans l'application enregistrée chez Microsoft Entra ou Google. Microsoft et Google n'acceptent que des URL en https.

SSO personnalisé : configuré dans l'application​

Okta, Keycloak, Microsoft, Google ou tout fournisseur OIDC déclaré dans SSO entreprise. L'URL de redirection est alors reconstruite à partir de la requête reçue : Traefik transmet à l'application l'adresse d'origine — en-têtes X-Forwarded-Proto, X-Forwarded-Host et X-Forwarded-Port — et FoxPlan en déduit https://VOTRE_DOMAINE/login/oauth2/code/<identifiant>. Le bouton Vérifier la configuration affiche l'URL exacte à déclarer.

Gardez le port public 443

Traefik remplace les en-têtes X-Forwarded-* par ce qu'il voit lui-même. Si les utilisateurs atteignent FoxPlan par un autre port public (:8443), ou si un équipement placé devant Traefik termine le TLS et lui transmet du HTTP, FoxPlan calcule une URL de redirection différente de celle du navigateur : le fournisseur refuse la connexion (redirect_uri mismatch). Un répartiteur placé devant Traefik doit donc lui transmettre le trafic en HTTPS sur le port 443, ou en TCP sans le déchiffrer, en conservant le nom de domaine d'origine.

Dimensionnement​

Les valeurs ci-dessous sont un peu au-dessus de ce que mesure FoxPlan en fonctionnement, pour garder de la marge sur les composants principaux.

Pour l'essai local​

RessourceRecommandé
Processeur4 vCPU
Mémoire allouée à Docker8 Go (6 Go au minimum)
Disque15 Go libres (images comprises)

À vide, l'essai consomme environ 1,5 Go, dont 1,1 Go pour l'application. Sous Docker Desktop, la mémoire allouée se règle dans Settings → Resources. L'essai ne fixe aucun plafond par conteneur : il se cale sur la mémoire donnée à Docker.

Pour l'installation entreprise​

UtilisateursProcesseurMémoireDisque (SSD)
Jusqu'à 1004 vCPU16 Go100 Go
De 100 à 5008 vCPU32 Go, avec 2 instances de l'application200 Go
Au-delàVoir Déploiement Kubernetes

Le disque comprend la base, les fichiers stockés dans Garage s'il est sur le même serveur, et la place pour les sauvegardes locales avant leur transfert. Pour placer la base sur un disque dédié, voir Volumes et disques.

Plafonds mémoire des conteneurs​

Le docker-compose.yml entreprise fixe un plafond mémoire par conteneur. Sans plafond, la JVM de l'application et MongoDB se dimensionnent chacun sur toute la mémoire du serveur — un quart pour la JVM, la moitié pour le cache de MongoDB — et finissent par se la disputer. Avec un plafond, chacun se cale sur le sien.

ConteneurPlafond par défautVariable (.env)
product-server (application)6 GoFOXPLAN_MEM_APP
mongo4 GoFOXPLAN_MEM_MONGO
gantt-export2 GoFOXPLAN_MEM_EXPORT
redis1 GoFOXPLAN_MEM_REDIS
gantt-msp512 MoFOXPLAN_MEM_MSP
docs256 MoFOXPLAN_MEM_DOCS
traefik256 MoFOXPLAN_MEM_TRAEFIK

Soit 14 Go, pour un serveur de 16 Go : il reste de quoi faire tourner le système. Sur un serveur de 32 Go, doublez FOXPLAN_MEM_APP et FOXPLAN_MEM_MONGO. L'application prend 75 % de son plafond pour son tas Java ; MongoDB réserve d'office la moitié du sien, moins 1 Go, à son cache.

Le processeur n'est pas plafonné : le démarrage de l'application et la génération des exports en profitent, et un plafond les ralentirait sans rien protéger.

Plusieurs instances de l'application​

Pour un grand nombre d'utilisateurs, lancez plusieurs instances de l'application sur le même serveur. Traefik les détecte et répartit les requêtes entre elles ; les sessions sont partagées dans Redis, donc un utilisateur peut passer d'une instance à l'autre sans être déconnecté. Les tâches planifiées (rapports, routines, synchronisations) prennent un verrou dans Redis : une seule instance les exécute, quel que soit leur nombre.

Dans .env :

FOXPLAN_APP_REPLICAS=2

puis docker compose up -d. Chaque instance a son propre plafond FOXPLAN_MEM_APP : la mémoire nécessaire augmente d'autant. Exemple pour un serveur de 32 Go avec 2 instances :

FOXPLAN_APP_REPLICAS=2
FOXPLAN_MEM_APP=6g # par instance : 12 Go au total
FOXPLAN_MEM_MONGO=8g

Soit environ 24 Go de plafonds au total, avec les autres conteneurs : il reste de la marge pour le système.

À quel moment ajouter une instance ? Quand le processeur du conteneur applicatif reste saturé aux heures de pointe (docker stats), en pratique au-delà d'une centaine d'utilisateurs actifs en même temps. Une deuxième instance garde aussi le service disponible si l'une redémarre.

Deux limites, propres à un serveur unique :

  • une montée de version recrée toutes les instances en même temps : l'interruption reste courte, mais elle existe ;
  • MongoDB reste un nœud unique, sur le même serveur.

Au-delà de 500 utilisateurs, ou pour une haute disponibilité réelle (plusieurs serveurs, déploiement progressif sans interruption), passez au Déploiement Kubernetes.

Volumes et disques​

La base MongoDB et les fichiers stockés dans Garage sont rangés dans des répertoires de l'hôte, jamais dans des volumes Docker. Un volume Docker disparaît avec docker compose down -v ou un docker system prune --volumes ; un répertoire de l'hôte, non. Placé sur un disque dédié, il survit aussi à une réinstallation de Docker ou du système.

DonnéesDans le conteneurEmplacement sur l'hôteVariable (.env)
Base MongoDB/data/dbRépertoire, obligatoireFOXPLAN_MONGO_DATA
Fichiers dans Garage (option)/var/lib/garage/meta, /var/lib/garage/dataRépertoire, sous-dossiers meta et dataFOXPLAN_GARAGE_DATA
Redis (cache, sessions)/dataVolume Docker redis_data, ou un répertoireFOXPLAN_REDIS_DATA (facultatif)
Certificats Traefik/certs, /acmeDossier ./traefik/ de l'installation—

Sans FOXPLAN_MONGO_DATA, docker compose refuse de démarrer avec un message qui la réclame. Le env.example propose ./data/mongo, dans le dossier d'installation : cela convient pour un pilote. En production, placez la base sur un disque dédié, comme décrit ci-dessous. Redis peut rester dans un volume Docker : il ne contient que le cache et les sessions, qui se reconstruisent seuls (perdus, ils ne font que déconnecter les utilisateurs).

Monter un disque dédié pour la base​

Les commandes suivantes s'exécutent sur le serveur, en administrateur, avant le premier démarrage de FoxPlan. Elles supposent un disque supplémentaire vide, en SSD ; un volume de baie (SAN en iSCSI ou Fibre Channel) convient aussi. Évitez les partages réseau NFS ou SMB : MongoDB y perd en performances et en fiabilité.

1. Repérer le disque. Il apparaît sans partition ni point de montage, par exemple sdb :

lsblk -f

2. Le formater en XFS, le système de fichiers recommandé par MongoDB (paquet xfsprogs). Cette commande efface le disque : vérifiez deux fois son nom.

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

3. Le monter de façon permanente, par son identifiant (UUID), qui ne change pas si l'ordre des disques change au redémarrage :

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 # doit afficher /dev/sdb et xfs

4. Faire attendre Docker que le disque soit monté. Sans cette précaution, si le disque n'est pas monté au démarrage du serveur, /srv/foxplan/mongo n'est qu'un dossier vide du disque système : MongoDB y démarre une base vide, et FoxPlan semble avoir tout perdu.

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 ne démarre alors plus tant que /srv/foxplan n'est pas monté.

5. Indiquer les répertoires dans .env :

FOXPLAN_MONGO_DATA=/srv/foxplan/mongo
FOXPLAN_GARAGE_DATA=/srv/foxplan/garage # seulement si vous ajoutez Garage

6. Préparer et démarrer. ./install.sh crée les répertoires (avec meta et data pour Garage) ; l'image MongoDB règle elle-même leurs droits.

./install.sh
docker compose up -d

7. Vérifier que la base écrit bien sur le disque dédié :

docker inspect foxplan-mongo --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
# /srv/foxplan/mongo -> /data/db
sudo ls /srv/foxplan/mongo # fichiers WiredTiger*, collection-*…
df -h /srv/foxplan # espace libre du disque de données

Surveillez ensuite l'espace libre : MongoDB qui manque de place s'arrête net, et l'application avec lui.

Installation existante : sortir la base d'un volume Docker​

Les installations antérieures rangeaient la base dans le volume Docker mongo_data. Après mise à jour du docker-compose.yml, renseignez FOXPLAN_MONGO_DATA puis lancez ./install.sh : s'il trouve le répertoire vide alors qu'une base existe dans l'ancien volume, il s'arrête et affiche la commande de copie à exécuter. En détail :

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

Le nom exact du volume se lit avec docker volume ls | grep mongo_data. Gardez l'ancien volume tant que vous n'avez pas vérifié l'application, puis supprimez-le (docker volume rm). Même principe pour Garage : recopiez garage_meta dans $FOXPLAN_GARAGE_DATA/meta et garage_data dans $FOXPLAN_GARAGE_DATA/data.

Installation​

1. Préparation​

chmod +x install.sh
./install.sh

Au premier lancement, le script crée .env depuis env.example et s'arrête pour vous laisser le renseigner.

VariableDescription
DOMAINDomaine servi, par exemple foxplan.mon-entreprise.fr
FOXPLAN_REGISTRYAdresse du registre d'images, communiquée par FoxPlan
FOXPLAN_IMAGENom de base des images — déjà renseigné : foxplan_prod
FOXPLAN_VERSIONVersion déployée — épinglez une version précise en production
MONGO_IMAGE / REDIS_IMAGEVersions validées des dépendances
FOXPLAN_MONGO_DATARépertoire de la base sur l'hôte — de préférence un disque dédié, voir Volumes et disques
TLS_MODEselfsigned, acme ou static — voir TLS
ACME_EMAIL, ACME_CA_SERVERAutorité de certification, si TLS_MODE=acme
latest en production

FOXPLAN_VERSION=latest suit la dernière version publiée : pratique pour un pilote, déconseillé ensuite. Un simple redémarrage de conteneur peut alors changer de version sans que vous l'ayez décidé. Épinglez une version, et faites vos montées de version explicitement.

Relancez ensuite ./install.sh : il génère le certificat par défaut et la configuration Traefik correspondant à votre TLS_MODE. Le script est idempotent — vous pouvez le rejouer à volonté, il ne remplace jamais un certificat ni un .env existant.

2. Secrets applicatifs​

Trois fichiers, à créer depuis les modèles téléchargés plus haut :

cp env_application.example .env_application # base de données, emails, S3, JWT…
cp env_mongo.example .env_mongo # identifiants MongoDB
cp env_redis.example .env_redis # mot de passe Redis

SECRET_KEY_JWT se génère une fois pour toutes :

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

Cohérences à respecter, sous peine d'échec au démarrage :

  • URL_MONGO reprend l'utilisateur et le mot de passe de .env_mongo, avec l'hôte mongo ;
  • SPRING_REDIS_PASSWORD est identique à REDIS_PASSWORD ;
  • BASE_URL vaut https:// suivi de DOMAIN — c'est lui qui construit les liens contenus dans les emails.
Deux fichiers, deux rôles

.env est lu par Docker Compose pour assembler la pile ; il n'est pas transmis au conteneur applicatif. Les variables de l'application — base, emails, S3, JWT — vont dans .env_application. Une variable applicative placée dans .env reste sans effet, silencieusement.

Envoi des emails​

FoxPlan envoie des invitations, des réinitialisations de mot de passe et des notifications. Sans configuration, aucun email ne part : l'application démarre et fonctionne, mais ces fonctions restent muettes.

À renseigner dans .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
Vérifier les envois depuis l'application

L'onglet Paramétrage → Entreprise → Emails montre, e-mail par e-mail, ce que votre serveur d'envoi a répondu, avec un bandeau vert / orange / rouge sur l'état de la plateforme. C'est le premier endroit à regarder quand un utilisateur dit n'avoir rien reçu — voir Journal des e-mails.

MAIL_PROXY_TYPE accepte aussi sendgrid ou gmail, pour un compte fourni par vos soins ; MAIL_PROXY_PARAMS porte alors <expéditeur>;<clé ou mot de passe>. Dans la très grande majorité des installations, smtp est le bon choix.

Deux points qui décident du sort de vos messages :

  • MAIL_FROM doit être autorisé à émettre pour votre domaine (SPF, DKIM). Un expéditeur non aligné part en indésirables, ou est rejeté sans que l'application en sache rien.
  • BASE_URL construit les liens des emails. S'il est absent ou faux, les messages partent correctement mais les liens qu'ils contiennent — invitation, réinitialisation — ne mènent nulle part.

Pour vérifier après démarrage, déclenchez une réinitialisation de mot de passe depuis l'écran de connexion et suivez les journaux :

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

Un échec d'envoi y apparaît en avertissement : l'application ne s'interrompt jamais pour un email non parti.

3. Démarrage​

./install.sh --start

Le script vérifie d'abord que toutes les images de la pile sont présentes sur le serveur. S'il en manque une, il la nomme et s'arrête sans rien démarrer : téléchargez-la (étape 1), puis relancez. Sinon, il démarre la pile avec docker compose up -d --pull never, sans contacter aucun registre.

Vérification :

docker compose ps
curl -I https://VOTRE_DOMAINE/
curl -I https://VOTRE_DOMAINE/docs/
Vérifier l'installation depuis l'application

Une fois connecté en administrateur, l'onglet Paramètres → Compte Foxplan → Diagnostic teste la base, Redis, l'envoi des e-mails (un message de test vous est adressé), le stockage S3 et le service d'export — voir Compte Foxplan.

4. Créer le premier compte​

Ouvrez https://VOTRE_DOMAINE : tant qu'aucune instance n'existe, une page de diagnostic s'ouvre (écran Première installation) et vous montre si tout fonctionne correctement — voir ce qui est vérifié. Si tout répond, cliquez sur Créer la première instance avec mon compte. FoxPlan envoie un code d'inscription à l'adresse saisie, par le serveur SMTP configuré dans .env_application : il arrive dans la vraie boîte de réception du destinataire. Saisissez ce code, suivez l'assistant (profil, entreprise, modules, invitations), puis choisissez votre mot de passe.

Ce premier compte est administrateur de l'instance : utilisez une adresse de l'équipe qui exploitera FoxPlan, pas un compte personnel temporaire.

Configurez l'envoi des emails avant de créer le compte

Sans SMTP fonctionnel, le code d'inscription ne part pas et aucun compte ne peut être créé. C'est pourquoi l'écran de première installation ne propose la création qu'une fois le serveur d'envoi joint. Vérifiez la section Envoi des emails, puis suivez les journaux pendant l'inscription : docker compose logs -f product-server | grep -i mail.

5. Vérifier l'installation : diagnostic​

Le diagnostic contrôle, pour l'essai comme pour l'installation entreprise :

ÉlémentContrôle
ApplicationLe serveur FoxPlan répond
Base de donnéesMongoDB répond
CacheRedis répond
Serveur d'envoi des emailsLe serveur SMTP est joignable (aucun email n'est envoyé)
Stockage des pièces jointesLe bucket S3 est accessible — « non configuré » s'il n'y en a pas
Export des planningsLe service gantt-export répond, testé depuis votre navigateur
DocumentationLe service docs répond, testé depuis votre navigateur

Il s'ouvre de deux façons :

  • Au premier lancement, tant qu'aucune instance n'a été créée : l'écran Première installation le lance de lui-même, puis propose de créer la première instance si aucun élément n'est en erreur. Un élément « non configuré », comme le stockage dans l'essai, ne bloque pas.
  • Ensuite, à tout moment : l'icône de diagnostic, à droite du choix de la langue sur la page de connexion. Elle n'apparaît que sur une installation on premise, et fonctionne sans être connecté — justement quand personne n'arrive à se connecter.

Sans connexion, le diagnostic n'affiche que des statuts, sans nom de serveur ni message technique : la page de connexion est visible de tous ceux qui l'atteignent. Le détail de chaque erreur, et un vrai email de test, sont dans le diagnostic complet, une fois connecté : Paramètres → Compte Foxplan → Diagnostic — voir Compte Foxplan.

Stockage des fichiers​

FoxPlan range ses fichiers dans un stockage objet compatible S3 : documents des projets, pièces jointes des contrats clients, photos de profil. Les fichiers y restent privés : l'application les relit et les sert elle-même, après contrôle des droits. Le stockage n'a donc pas à être joignable depuis Internet, ni à gérer de permissions publiques.

Variable (.env_application)Rôle
FILE_S3_URLAdresse du service S3, par exemple http://garage:3900
FILE_S3_BUCKETBucket des fichiers
FILE_S3_ACCESS_KEY, FILE_S3_SECRET_KEYIdentifiants d'accès au bucket
FILE_S3_REGIONRégion signée dans chaque requête — celle qu'attend votre stockage. Défaut : fr-par

Sans stockage, laissez ces variables vides : l'application fonctionne, seuls les fichiers sont indisponibles. Un fichier ne peut dépasser 10 Mo. FoxPlan n'applique aucun quota on premise : l'espace est le vôtre.

Votre stockage existant​

Tout stockage compatible S3 convient : Ceph, NetApp StorageGRID, Scality, MinIO, AWS… Créez un bucket et des identifiants dédiés à FoxPlan, avec les droits lecture, écriture et suppression sur ce bucket, puis renseignez les variables ci-dessus.

Vous avez déjà un stockage S3 : ne modifiez pas le docker-compose.yml

L'installation entreprise ne contient pas Garage : elle se branche sur le stockage que vous désignez. Le docker-compose.yml reste tel quel, ne créez pas de docker-compose.override.yml ; toute la configuration va dans .env_application :

# .env_application — votre stockage S3
FILE_S3_URL=https://s3.mon-entreprise.local # adresse du service S3, sans le nom du bucket
FILE_S3_BUCKET=foxplan # bucket créé pour FoxPlan
FILE_S3_ACCESS_KEY=<identifiant d'accès>
FILE_S3_SECRET_KEY=<clé secrète>
FILE_S3_REGION=<région attendue par le stockage> # ex. us-east-1, eu-west-3, ou celle de votre Ceph/MinIO

Puis appliquez la configuration : docker compose up -d product-server (le conteneur est recréé avec les nouvelles valeurs).

  • Le serveur Docker doit joindre l'adresse FILE_S3_URL (DNS, pare-feu) : c'est l'application qui lit et écrit les fichiers, jamais le navigateur.
  • En https, le certificat du stockage doit être signé par une autorité reconnue ; un certificat interne non reconnu fait échouer la connexion.
  • L'application adresse le bucket dans le chemin (https://s3.mon-entreprise.local/foxplan/…) : aucun DNS par bucket n'est nécessaire.
  • Vérifiez le résultat dans le diagnostic : la ligne Stockage doit être au vert.
La région doit être celle du stockage

La région entre dans la signature de chaque requête. Si FILE_S3_REGION diffère de celle qu'attend le stockage, toutes les requêtes sont refusées (erreur 400 dans les journaux). Le diagnostic le signale : stockage en erreur.

Garage, si vous n'en avez pas​

Garage est un stockage S3 open source et léger, développé en France. L'essai local l'intègre déjà. Pour l'ajouter à l'installation entreprise sans toucher au docker-compose.yml, créez à côté un fichier docker-compose.override.yml, que Docker Compose lit automatiquement :

services:
garage:
image: dxflrs/garage:v2.1.0
restart: always
volumes:
- ./garage.toml:/etc/garage.toml:ro
# Répertoires de l'hôte (FOXPLAN_GARAGE_DATA dans .env) : ils survivent à « docker compose down -v ».
- ${FOXPLAN_GARAGE_DATA:?Renseignez FOXPLAN_GARAGE_DATA dans .env}/meta:/var/lib/garage/meta
- ${FOXPLAN_GARAGE_DATA:?Renseignez FOXPLAN_GARAGE_DATA dans .env}/data:/var/lib/garage/data
networks:
- foxplan-network
deploy:
resources:
limits:
memory: 1g

Renseignez dans .env le répertoire des fichiers, de préférence sur le disque de données (voir Volumes et disques), puis relancez ./install.sh, qui crée ses sous-dossiers meta et data :

FOXPLAN_GARAGE_DATA=/srv/foxplan/garage

Créez ensuite le fichier garage.toml, avec deux secrets générés pour votre installation :

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

Démarrez Garage, puis préparez-le — topologie, bucket et clé d'accès :

docker compose up -d garage
docker compose exec garage /garage status # relevez l'ID du nœud (première colonne)
docker compose exec garage /garage layout assign -z dc1 -c 100G <ID_DU_NOEUD>
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 # affiche « Key ID » et « Secret key »
docker compose exec garage /garage bucket allow --read --write --owner foxplan --key foxplan

-c 100G est la capacité annoncée du nœud : ajustez-la à l'espace disque réservé aux fichiers. Reportez enfin la clé dans .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

puis redémarrez l'application :

docker compose up -d product-server

Le diagnostic (icône de la page de connexion, ou Compte Foxplan → Diagnostic) doit afficher le stockage en OK. Garage n'est joignable que depuis le réseau interne du compose : aucun port n'est publié.

Une valeur faite de chiffres se met entre guillemets

Dans un fichier YAML, une valeur composée uniquement de chiffres est lue comme un nombre : une clé 0000…01 deviendrait 1. Si vous placez une clé dans un fichier docker-compose, mettez-la entre guillemets. Dans .env_application, le problème ne se pose pas.

Certificats entrants et sortants​

FoxPlan utilise deux certificats différents, un par sens de circulation. Ils ne vont ni au même endroit, ni dans le même composant.

Flux ENTRANTS navigateur ──HTTPS──► Traefik ──► application
Traefik présente le certificat DE FOXPLAN → traefik/certs/

Flux SORTANTS application ──HTTPS, SMTP TLS──► AD FS, SMTP, S3 internes…
l'application vérifie LEUR certificat grâce à → ca-sortant/
l'autorité qui les a signés
Flux entrantsFlux sortants
RôleProuver aux navigateurs que le serveur est bien DOMAINPermettre à l'application de reconnaître vos services internes
Utilisé parTraefikL'application (Java)
ContenuLe certificat du serveur FoxPlan et sa clé privéeLe certificat public de l'autorité — racine, et intermédiaires le cas échéant. Jamais de clé privée
Emplacementtraefik/certs/tls.crt et traefik/certs/tls.keyca-sortant/<nom>.crt — un ou plusieurs fichiers ; autre répertoire possible avec FOXPLAN_OUTBOUND_CA_DIR dans .env
FormatPEM. tls.crt : certificat du serveur suivi de ses intermédiaires. tls.key : clé non chiffrée, sans phrase de passePEM, extension .crt obligatoire. Un fichier peut contenir plusieurs certificats
ObligatoireOui — install.sh génère un auto-signé en attendant le vôtreSeulement si un service appelé est signé par une autorité non publique
Prise en compteÀ chaud, après touch traefik/dynamic/tls.yml — voir le mode staticAu démarrage : ./install.sh (contrôle des fichiers), puis docker compose restart product-server

Un fichier PEM est un fichier texte qui ne contient que des blocs -----BEGIN CERTIFICATE----- … -----END CERTIFICATE----- (et, pour la clé, -----BEGIN PRIVATE KEY-----).

Est-ce le même certificat ?​

Non. Le certificat entrant est celui de FoxPlan, accompagné de sa clé privée. Le certificat sortant est celui de l'autorité qui a signé les certificats d'AD FS, du SMTP ou du S3 : public, sans clé.

Ils viennent en revanche souvent de la même PKI. Si l'autorité interne a signé à la fois le certificat de FoxPlan et celui d'AD FS, le certificat de son autorité racine sert :

  • dans ca-sortant/, pour que l'application reconnaisse AD FS, le SMTP… ;
  • dans traefik/ca/, uniquement en mode acme avec une PKI interne, pour que Traefik reconnaisse le serveur ACME.

Ne copiez jamais tls.crt ni tls.key dans ca-sortant/ : install.sh refuse tout fichier qui contient une clé privée.

Une autorité sortante est-elle nécessaire ?​

Depuis le serveur FoxPlan, affichez l'émetteur du certificat de chaque service interne :

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

Émetteur public (DigiCert, Sectigo, GlobalSign, Let's Encrypt…) : rien à faire. Émetteur interne : demandez à l'équipe PKI le certificat de l'autorité racine et de ses intermédiaires, au format PEM, et déposez-le dans ca-sortant/.

Si elle manque, la connexion SSO échoue au retour du fournisseur, ou les emails ne partent pas, avec PKIX path building failed dans docker compose logs product-server.

Convertir au format PEM​

Fichier reçuCommande
.cer ou .der binaireopenssl x509 -inform DER -in autorite.cer -out ca-sortant/autorite.crt
.p7b (chaîne d'autorités)openssl pkcs7 -print_certs -in chaine.p7b | sed -n '/-----BEGIN/,/-----END/p' > ca-sortant/autorite.crt
.pfx ou .p12 (certificat de FoxPlan avec sa clé)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

Le sed retire les lignes descriptives (Bag Attributes, subject=…) qu'ajoute openssl. Dans ca-sortant/, elles empêcheraient l'application de démarrer : install.sh les détecte et s'arrête en donnant la commande de nettoyage. Avec le .pfx, vérifiez que tls.crt commence bien par le certificat du serveur, avant ses intermédiaires.

TLS : trois modes​

Quel que soit le mode, install.sh installe un certificat par défaut. Traefik le sert dès le premier démarrage, et continue de le servir si l'obtention automatique échoue : le site ne tombe jamais par manque de certificat, il dégrade au pire vers un avertissement navigateur.

Le choix se fait par la variable TLS_MODE, et tient à une seule question : qui émet vos certificats, et Traefik peut-il lui parler ?

TLS_MODEAutoritéAccès sortantRenouvellementUsage
selfsignedAucuneAucun—Évaluation, pilote
acmeLet's Encrypt ou votre PKIVers l'autoritéAutomatiqueProduction
staticQuelconqueAucunÀ votre chargeRéseau fermé, wildcard d'entreprise

selfsigned — démarrer immédiatement​

Mode par défaut. install.sh génère un certificat auto-signé pour votre DOMAIN, valable 825 jours. L'application est joignable en HTTPS tout de suite, sans DNS public ni ouverture de pare-feu.

Les navigateurs afficheront un avertissement : c'est attendu. Ce mode sert à valider l'installation, pas à exposer le service à des utilisateurs.

acme — renouvellement automatique​

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

Traefik obtient le certificat par un challenge HTTP-01 et le renouvelle seul. Avec Let's Encrypt, deux conditions : le domaine doit résoudre publiquement vers ce serveur, et le port 80 doit être joignable depuis Internet — le challenge y passe, même si tout le trafic est ensuite redirigé vers 443.

Avec votre PKI interne, rien ne change sinon l'adresse. Si votre autorité expose un service ACME — step-ca, HashiCorp Vault, EJBCA — le protocole est le même (RFC 8555) :

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

Si cette autorité est privée, Traefik ne la connaît pas et la négociation échouerait sur un certificat inconnu. Fournissez-lui la chaîne de confiance :

cp /chemin/vers/ca-interne.crt traefik/ca/ca-interne.crt

puis, dans .env :

LEGO_CA_CERTIFICATES=/ca/ca-interne.crt

install.sh vérifie la présence du fichier et vous avertit s'il manque.

C'est le mode à privilégier dans un réseau fermé : vous gardez le renouvellement automatique sans aucune dépendance à Internet, à la seule condition que le serveur atteigne votre autorité.

Certificats de courte durée

Les PKI internes émettent souvent des certificats valables quelques jours. C'est sans conséquence : Traefik renouvelle d'autant plus souvent.

static — votre certificat, votre renouvellement​

Pour un certificat obtenu hors ligne — wildcard d'entreprise, certificat commercial, autorité interne sans service ACME. Aucun ACME n'est configuré : Traefik ne sort jamais sur le réseau pour obtenir un certificat.

Écrasez simplement les fichiers générés par les vôtres :

cp votre-certificat.crt traefik/certs/tls.crt # chaîne complète
cp votre-cle.key traefik/certs/tls.key
chmod 600 traefik/certs/tls.key
Chaîne complète

tls.crt doit contenir le certificat du serveur suivi de ses intermédiaires, dans cet ordre. Un certificat servi sans sa chaîne fonctionne dans un navigateur de bureau — qui la complète souvent tout seul — mais échoue sur mobile, en ligne de commande et sur les appels d'API. Symptôme classique et déroutant : « ça marche chez moi ».

Le renouvellement est à votre charge. Traefik ne surveille pas les fichiers de certificat, seulement sa configuration dans traefik/dynamic/ : après avoir remplacé les deux fichiers, touchez cette configuration pour qu'il les relise à chaud, sans redémarrage ni coupure.

touch traefik/dynamic/tls.yml

Après le remplacement, Traefik continue sinon de servir l'ancien certificat, jusqu'à son prochain redémarrage — ou jusqu'à son expiration. Si votre PKI dépose les certificats renouvelés dans un répertoire, la tâche qui les copie dans traefik/certs/ doit se terminer par ce touch.

Changer de mode​

Modifiez TLS_MODE dans .env, relancez ./install.sh, puis :

docker compose up -d traefik

Le certificat existant est conservé : pour repartir d'un certificat neuf, supprimez traefik/certs/ avant de relancer le script.

Vérifier le certificat servi​

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

Si l'émetteur est votre propre domaine alors que vous attendiez une autorité, c'est le certificat par défaut qui est servi : l'obtention a échoué. Dans l'ordre — le DNS (dig +short VOTRE_DOMAINE), l'ouverture des ports, puis docker compose logs traefik | grep -iE 'acme|certificate|error'.

Exploitation courante​

docker compose ps # état des services
docker compose logs -f product-server # journaux de l'application
./install.sh --start # montée de version, après l'étape 1
docker compose down # arrêt, données conservées

La base MongoDB et les fichiers de Garage vivent dans des répertoires de l'hôte (FOXPLAN_MONGO_DATA, FOXPLAN_GARAGE_DATA, voir Volumes et disques) : ils survivent aux arrêts, aux montées de version et à docker compose down -v. Une montée de version se fait elle aussi en deux étapes : téléchargez les images de la nouvelle version (étape 1), changez FOXPLAN_VERSION dans .env, puis relancez ./install.sh --start.

Sauvegardes

Un répertoire de données n'est pas une sauvegarde : il ne protège ni d'une panne du disque, ni d'une suppression par erreur. Voir Sauvegarde et restauration.

Sauvegarde et restauration​

Ce qu'il faut sauvegarder​

ÉlémentÀ sauvegarderPourquoi
MongoDBOuiToute la donnée métier : projets, tâches, budgets, utilisateurs
Fichiers .env*OuiContiennent des clés sans lesquelles la donnée restaurée est inexploitable
RedisNonCache et sessions : se reconstruit seul, au prix d'une reconnexion des utilisateurs
FichiersOui, en même temps que la baseDocuments, pièces jointes, photos de profil : dans le stockage S3 (FILE_S3_*), pas dans MongoDB. Avec Garage, voir Fichiers stockés dans Garage
Sauvegardez .env_application avec la base

SECRET_KEY_JWT sert aussi, par défaut, de clé de chiffrement des secrets SSO stockés en base. Restaurer un dump MongoDB sans cette clé rend les configurations SSO indéchiffrables : elles sont là, illisibles, et à ressaisir intégralement. Une sauvegarde de base sans les secrets n'est pas une sauvegarde complète.

Sauvegarde​

La commande s'exécute dans le conteneur MongoDB, qui embarque les outils nécessaires :

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

Trois détails qui comptent :

  • Les identifiants sont lus depuis l'environnement du conteneur, jamais écrits sur la ligne de commande — ils n'apparaissent donc ni dans ps ni dans l'historique du shell. Un mot de passe contenant espaces ou caractères spéciaux passe ainsi sans échappement.
  • --archive produit un seul fichier plutôt qu'une arborescence : plus simple à transférer, à chiffrer et à vérifier.
  • -T désactive l'allocation d'un pseudo-terminal, faute de quoi le flux binaire serait corrompu à la redirection.

La sauvegarde se fait à chaud, sans arrêter l'application.

Restauration​

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 remplace chaque collection par la version sauvegardée. Sans cette option, la restauration fusionnerait avec les données présentes, ce qui donne un état incohérent — c'est rarement ce que l'on veut.

Vider le cache Redis après toute restauration

Redis conserve des objets qui référencent les identifiants de l'ancienne base. Après une restauration, ces références ne correspondent plus à rien et l'application renvoie des erreurs 500 déroutantes — typiquement un objet « introuvable » alors qu'il est bien présent en base.

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

C'est l'oubli le plus fréquent après une restauration.

Fichiers stockés dans Garage​

MongoDB référence les fichiers par leur clé : sauvegardez la base et les fichiers au même moment, pour pouvoir les restaurer ensemble.

À chaud, fichier par fichier — la méthode à privilégier. rclone recopie le contenu du bucket dans un répertoire, sans arrêter Garage ; les passages suivants ne transfèrent que ce qui a changé. Avec les identifiants 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"

Pour restaurer, inversez la source et la destination (sync /backup :s3:foxplan), sur un Garage préparé comme à l'installation.

À froid, répertoire complet — pour une migration de serveur. Arrêtez Garage, archivez son répertoire (FOXPLAN_GARAGE_DATA, avec ses sous-dossiers meta et data), puis redémarrez-le :

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

Les deux sous-dossiers vont ensemble : des données sans leurs métadonnées sont inutilisables. Comme pour MongoDB, transférez les sauvegardes hors du serveur.

Automatiser​

Une tâche cron quotidienne, avec rotation sur 30 jours :

# /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

Le % doit être échappé en \% dans une table cron, sans quoi la ligne est tronquée à cet endroit.

Une sauvegarde locale ne protège de rien

Une archive posée sur le serveur qu'elle sauvegarde disparaît avec lui. Transférez-la vers un stockage distinct — stockage objet, serveur de sauvegarde, bande. C'est la moitié du travail, et c'est celle qu'on oublie.

Vérifier que la sauvegarde est exploitable​

Une sauvegarde jamais restaurée n'est pas une sauvegarde : c'est une hypothèse. Testez-la périodiquement sur un environnement séparé — jamais sur la production.

# Contrôle rapide de l'intégrité de l'archive, sans rien restaurer
gzip -t foxplan-2026-09-15-0300.archive.gz && echo "archive intègre"

Puis, une à deux fois par an, déroulez une restauration complète sur un serveur de test et connectez-vous à l'application : c'est le seul contrôle qui prouve quelque chose.

Alternative : sauvegarde à froid de la base​

Pour une copie bit à bit plutôt qu'un export logique, arrêtez la base et archivez son répertoire (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

Plus rapide sur une grosse base, mais avec deux contreparties : l'application est interrompue pendant l'opération, et l'archive n'est restaurable que sur une version de MongoDB identique. Le mongodump reste la méthode à privilégier ; réservez celle-ci aux migrations de serveur.

Mode maintenance​

Une page de maintenance activable, qui laisse /docs accessible pendant l'intervention, est fournie avec le package de déploiement accompagné. Elle n'est pas incluse dans les fichiers téléchargeables ci-dessus. Contactez le support pour l'obtenir.

Dépannage​

SymptômePiste
manifest unknown au démarrageFOXPLAN_VERSION inexistante
pull access deniedFOXPLAN_REGISTRY ou FOXPLAN_IMAGE erroné — l'image s'appelle foxplan_prod — ou ancien docker login refusé sur le registre : docker logout <hôte du registre>
required variable FOXPLAN_REGISTRY is missingAdresse du registre absente de .env : renseignez la valeur communiquée par FoxPlan
env file .env_mongo not foundLes fichiers de secrets n'ont pas été créés — voir Secrets applicatifs
404 sur /DOMAIN incorrect, ou application non démarrée — voir les journaux
502 / 503L'application démarre encore, ou s'est arrêtée sur une erreur
Certificat invalideDNS, ports, journaux ACME — voir Vérifier le certificat servi
Doute sur une intégrationCompte Foxplan → Diagnostic : base, Redis, e-mails, S3 et export testés en un clic
Aucun email reçuMAIL_PROXY_TYPE ou MAIL_SMTP_* absents — voir Envoi des emails
Emails reçus mais liens invalidesBASE_URL absent ou différent du domaine public
Emails en indésirablesMAIL_FROM non autorisé à émettre pour le domaine (SPF/DKIM)
Erreur d'authentification RedisSPRING_REDIS_PASSWORD différent de REDIS_PASSWORD
Échec d'authentification MongoURL_MONGO incohérent avec .env_mongo
Exports Gantt en échecService gantt-export arrêté, ou MSP_SERVICE_ENDPOINT inaccessible
Erreurs 500 « objet introuvable » après une restaurationCache Redis non vidé — voir Restauration
Configuration SSO illisible après restaurationSECRET_KEY_JWT différent de celui en vigueur lors du dump
Stockage en erreur au diagnostic, 400 sur les requêtes S3FILE_S3_REGION différent de la région du stockage (s3_region pour Garage) — voir Stockage des fichiers
Stockage en erreur juste après un changement de clé GarageLa clé de .env_application n'est pas celle de Garage : docker compose exec garage /garage key info foxplan --show-secret
« Fichier trop volumineux »Un fichier dépasse 10 Mo
« Quota de stockage atteint »SaaS uniquement : l'instance a utilisé son espace ; une licence peut l'étendre
no matching manifest for linux/arm64Images d'export publiées en amd64 seulement : gardez platform: linux/amd64 sur gantt-export et gantt-msp (Docker Desktop les émule)
L'application redémarre en boucle, Client id of registration 'google' must not be empty dans les journauxImage antérieure à octobre 2026 et lignes SSO Microsoft ou Google déclarées mais vides dans .env_application : mettez l'image à jour, ou commentez ces lignes
FoxPlan repart sans données après un redémarrage du serveurDisque de données non monté : MongoDB a démarré sur le dossier vide du disque système. findmnt /srv/foxplan, puis l'étape 4 de Volumes et disques
required variable FOXPLAN_MONGO_DATA is missing a valueVariable absente de .env : voir Volumes et disques
MongoDB vide après avoir renseigné FOXPLAN_MONGO_DATADonnées restées dans l'ancien volume Docker : voir Installation existante
L'application redémarre en boucle, https://${FILE_S3_URL} dans les journauxVariables FILE_S3_* absentes de .env_application : déclarez-les, même vides — les pièces jointes seront alors indisponibles
PKIX path building failed dans les journaux de l'applicationAutorité interne d'AD FS, du SMTP ou du S3 absente de ca-sortant/ : voir Certificats entrants et sortants