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 local | Installation entreprise | |
|---|---|---|
| Pour | Évaluer FoxPlan sur un poste | Un serveur partagé, la production |
| Fichiers | un seul docker-compose.yml | docker-compose.yml, install.sh et des fichiers .env |
| Configuration | Aucune | Domaine, TLS, SMTP, secrets |
| Accès | http://localhost:8090, depuis le poste seulement | https:// votre domaine |
| Emails | Captés par une boîte de test intégrée | Envoyés par votre serveur SMTP |
Les deux versions utilisent les mêmes images et s'installent en deux étapes distinctes :
- 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 ;
- 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.
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.
| Image | Rôle |
|---|---|
foxplan_prod | Application |
foxplan_prod-docs | Cette documentation |
foxplan_prod-export | Export des plannings (PDF, PNG, Excel, iCal) |
foxplan_prod-msp | Import 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 :
| Image | Essai local | Installation entreprise |
|---|---|---|
traefik:v3.6.4 | Oui | Oui |
mongo:8.2.7 | Oui | Oui (MONGO_IMAGE) |
redis:7.4 | Oui | Oui (REDIS_IMAGE) |
axllent/mailpit:v1.27 | Oui | Non : vos emails partent par votre serveur SMTP |
dxflrs/garage:v2.1.0 | Oui | Seulement si vous ajoutez Garage — voir Stockage des fichiers |
curlimages/curl:8.14.1 | Oui : initialise Garage au premier démarrage | Non |
rclone/rclone:1.68.2 | Non | Seulement 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) :
| Destination | Rôle | Images concernées |
|---|---|---|
| Hôte du registre FoxPlan | Index des images | foxplan_prod, -docs, -export, -msp |
| Service de jetons du registre FoxPlan | Jeton d'accès anonyme | idem |
| Stockage objet du registre FoxPlan | Couches des images : le registre redirige les téléchargements vers ce stockage | idem |
registry-1.docker.io, auth.docker.io | Docker Hub : index des images et jeton d'accès | Images tierces |
production.cloudflare.docker.com, production.cloudfront.docker.com | Couches des images Docker Hub (CDN) | idem |
app.fox-plan.com | Téléchargement des fichiers de déploiement de cette page | Essai 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
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 :
| Adresse | Contenu |
|---|---|
http://localhost:8090 | FoxPlan — 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.
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.
- 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é. - 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.
- Dans un autre onglet, ouvrez
http://localhost:8090/mailpit/: le message contenant le code d'inscription y arrive en quelques secondes. - 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.
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_mongoet.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)
| Service | Rôle | Indispensable |
|---|---|---|
product-server | Application FoxPlan | Oui |
docs | Cette documentation, servie sous /docs | Non |
gantt-export | Export des plannings en PDF / PNG / Excel / iCal | Non |
gantt-msp | Import et export MS Project et Primavera | Non |
mongo | Base de données | Oui |
redis | Cache et sessions partagées | Oui |
| Stockage S3 | Fichiers : documents, pièces jointes, photos de profil — le vôtre ou Garage, voir Stockage des fichiers | Non |
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.
| Sens | Port | Entre | Rôle |
|---|---|---|---|
| Entrant | 443 (TCP) | Postes des utilisateurs → Traefik | L'application et le retour du SSO |
| Entrant | 80 (TCP) | Postes des utilisateurs → Traefik | Redirection vers 443 ; challenge HTTP-01 en mode acme |
| Sortant | 443 (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.
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
| Ressource | Recommandé |
|---|---|
| Processeur | 4 vCPU |
| Mémoire allouée à Docker | 8 Go (6 Go au minimum) |
| Disque | 15 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
| Utilisateurs | Processeur | Mémoire | Disque (SSD) |
|---|---|---|---|
| Jusqu'à 100 | 4 vCPU | 16 Go | 100 Go |
| De 100 à 500 | 8 vCPU | 32 Go, avec 2 instances de l'application | 200 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.
| Conteneur | Plafond par défaut | Variable (.env) |
|---|---|---|
product-server (application) | 6 Go | FOXPLAN_MEM_APP |
mongo | 4 Go | FOXPLAN_MEM_MONGO |
gantt-export | 2 Go | FOXPLAN_MEM_EXPORT |
redis | 1 Go | FOXPLAN_MEM_REDIS |
gantt-msp | 512 Mo | FOXPLAN_MEM_MSP |
docs | 256 Mo | FOXPLAN_MEM_DOCS |
traefik | 256 Mo | FOXPLAN_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ées | Dans le conteneur | Emplacement sur l'hôte | Variable (.env) |
|---|---|---|---|
| Base MongoDB | /data/db | Répertoire, obligatoire | FOXPLAN_MONGO_DATA |
| Fichiers dans Garage (option) | /var/lib/garage/meta, /var/lib/garage/data | Répertoire, sous-dossiers meta et data | FOXPLAN_GARAGE_DATA |
| Redis (cache, sessions) | /data | Volume Docker redis_data, ou un répertoire | FOXPLAN_REDIS_DATA (facultatif) |
| Certificats Traefik | /certs, /acme | Dossier ./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.
| Variable | Description |
|---|---|
DOMAIN | Domaine servi, par exemple foxplan.mon-entreprise.fr |
FOXPLAN_REGISTRY | Adresse du registre d'images, communiquée par FoxPlan |
FOXPLAN_IMAGE | Nom de base des images — déjà renseigné : foxplan_prod |
FOXPLAN_VERSION | Version déployée — épinglez une version précise en production |
MONGO_IMAGE / REDIS_IMAGE | Versions validées des dépendances |
FOXPLAN_MONGO_DATA | Répertoire de la base sur l'hôte — de préférence un disque dédié, voir Volumes et disques |
TLS_MODE | selfsigned, acme ou static — voir TLS |
ACME_EMAIL, ACME_CA_SERVER | Autorité de certification, si TLS_MODE=acme |
latest en productionFOXPLAN_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_MONGOreprend l'utilisateur et le mot de passe de.env_mongo, avec l'hôtemongo;SPRING_REDIS_PASSWORDest identique àREDIS_PASSWORD;BASE_URLvauthttps://suivi deDOMAIN— c'est lui qui construit les liens contenus dans les emails.
.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
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_FROMdoit ê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_URLconstruit 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/
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.
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ément | Contrôle |
|---|---|
| Application | Le serveur FoxPlan répond |
| Base de données | MongoDB répond |
| Cache | Redis répond |
| Serveur d'envoi des emails | Le serveur SMTP est joignable (aucun email n'est envoyé) |
| Stockage des pièces jointes | Le bucket S3 est accessible — « non configuré » s'il n'y en a pas |
| Export des plannings | Le service gantt-export répond, testé depuis votre navigateur |
| Documentation | Le 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_URL | Adresse du service S3, par exemple http://garage:3900 |
FILE_S3_BUCKET | Bucket des fichiers |
FILE_S3_ACCESS_KEY, FILE_S3_SECRET_KEY | Identifiants d'accès au bucket |
FILE_S3_REGION | Ré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.
docker-compose.ymlL'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 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é.
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 entrants | Flux sortants | |
|---|---|---|
| Rôle | Prouver aux navigateurs que le serveur est bien DOMAIN | Permettre à l'application de reconnaître vos services internes |
| Utilisé par | Traefik | L'application (Java) |
| Contenu | Le certificat du serveur FoxPlan et sa clé privée | Le certificat public de l'autorité — racine, et intermédiaires le cas échéant. Jamais de clé privée |
| Emplacement | traefik/certs/tls.crt et traefik/certs/tls.key | ca-sortant/<nom>.crt — un ou plusieurs fichiers ; autre répertoire possible avec FOXPLAN_OUTBOUND_CA_DIR dans .env |
| Format | PEM. tls.crt : certificat du serveur suivi de ses intermédiaires. tls.key : clé non chiffrée, sans phrase de passe | PEM, extension .crt obligatoire. Un fichier peut contenir plusieurs certificats |
| Obligatoire | Oui — install.sh génère un auto-signé en attendant le vôtre | Seulement 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 static | Au 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 modeacmeavec 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çu | Commande |
|---|---|
.cer ou .der binaire | openssl 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.crtopenssl 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_MODE | Autorité | Accès sortant | Renouvellement | Usage |
|---|---|---|---|---|
selfsigned | Aucune | Aucun | — | Évaluation, pilote |
acme | Let's Encrypt ou votre PKI | Vers l'autorité | Automatique | Production |
static | Quelconque | Aucun | À votre charge | Ré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é.
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
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.
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 | À sauvegarder | Pourquoi |
|---|---|---|
| MongoDB | Oui | Toute la donnée métier : projets, tâches, budgets, utilisateurs |
Fichiers .env* | Oui | Contiennent des clés sans lesquelles la donnée restaurée est inexploitable |
| Redis | Non | Cache et sessions : se reconstruit seul, au prix d'une reconnexion des utilisateurs |
| Fichiers | Oui, en même temps que la base | Documents, pièces jointes, photos de profil : dans le stockage S3 (FILE_S3_*), pas dans MongoDB. Avec Garage, voir Fichiers stockés dans Garage |
.env_application avec la baseSECRET_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
psni dans l'historique du shell. Un mot de passe contenant espaces ou caractères spéciaux passe ainsi sans échappement. --archiveproduit un seul fichier plutôt qu'une arborescence : plus simple à transférer, à chiffrer et à vérifier.-Tdé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.
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 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ôme | Piste |
|---|---|
manifest unknown au démarrage | FOXPLAN_VERSION inexistante |
pull access denied | FOXPLAN_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 missing | Adresse du registre absente de .env : renseignez la valeur communiquée par FoxPlan |
env file .env_mongo not found | Les 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 / 503 | L'application démarre encore, ou s'est arrêtée sur une erreur |
| Certificat invalide | DNS, ports, journaux ACME — voir Vérifier le certificat servi |
| Doute sur une intégration | Compte Foxplan → Diagnostic : base, Redis, e-mails, S3 et export testés en un clic |
| Aucun email reçu | MAIL_PROXY_TYPE ou MAIL_SMTP_* absents — voir Envoi des emails |
| Emails reçus mais liens invalides | BASE_URL absent ou différent du domaine public |
| Emails en indésirables | MAIL_FROM non autorisé à émettre pour le domaine (SPF/DKIM) |
| Erreur d'authentification Redis | SPRING_REDIS_PASSWORD différent de REDIS_PASSWORD |
| Échec d'authentification Mongo | URL_MONGO incohérent avec .env_mongo |
| Exports Gantt en échec | Service gantt-export arrêté, ou MSP_SERVICE_ENDPOINT inaccessible |
| Erreurs 500 « objet introuvable » après une restauration | Cache Redis non vidé — voir Restauration |
| Configuration SSO illisible après restauration | SECRET_KEY_JWT différent de celui en vigueur lors du dump |
Stockage en erreur au diagnostic, 400 sur les requêtes S3 | FILE_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é Garage | La 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/arm64 | Images 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 journaux | Image 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 serveur | Disque 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 value | Variable absente de .env : voir Volumes et disques |
MongoDB vide après avoir renseigné FOXPLAN_MONGO_DATA | Données restées dans l'ancien volume Docker : voir Installation existante |
L'application redémarre en boucle, https://${FILE_S3_URL} dans les journaux | Variables 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'application | Autorité interne d'AD FS, du SMTP ou du S3 absente de ca-sortant/ : voir Certificats entrants et sortants |