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 le format Kubernetes.

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
  • env.example — toutes les variables, à enregistrer sous le nom .env

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

Accès au registre d'images

L'adresse du registre (FOXPLAN_REGISTRY) et les identifiants de lecture vous sont communiqués avec votre souscription on-premise, en même temps que votre licence. Ils ne figurent pas dans cette documentation.

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

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

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 ;
  • Un accès en lecture au registre d'images FoxPlan, et votre licence ;
  • Selon le mode TLS retenu, un accès sortant vers l'autorité de certification (voir plus bas).

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_REGISTRYRegistre d'images, communiqué avec votre souscription
FOXPLAN_IMAGENom de base des images (fourni avec le registre)
FOXPLAN_VERSIONVersion déployée — épinglez une version précise en production
MONGO_IMAGE / REDIS_IMAGEVersions validées des dépendances
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, fournis avec le package de déploiement sous forme de modèles .example :

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

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

docker login "$FOXPLAN_REGISTRY"
docker compose up -d

ou, en une fois : ./install.sh --start.

Vérification :

docker compose ps
curl -I https://VOTRE_DOMAINE/
curl -I https://VOTRE_DOMAINE/docs/

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 surveille le répertoire et relit les certificats à chaud : remplacer les deux fichiers suffit, aucun redémarrage n'est nécessaire.

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
docker compose pull && docker compose up -d # montée de version
docker compose down # arrêt, données conservées

Les données MongoDB et Redis vivent dans des volumes nommés qui survivent aux arrêts et aux montées de version. Une montée de version consiste à changer FOXPLAN_VERSION dans .env, puis à relancer les deux commandes ci-dessus.

Sauvegardes

Les volumes Docker ne sont pas une sauvegarde : ils disparaissent avec un docker compose down -v. 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
Pièces jointesSéparémentStockées dans le stockage objet S3 configuré par FILE_S3_*, pas dans MongoDB
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.

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 du volume

Pour une copie bit à bit plutôt qu'un export logique, arrêtez la base et archivez son volume.

Son nom est préfixé par le nom du projet Compose, lui-même dérivé du répertoire d'installation : relevez-le plutôt que de le deviner.

docker volume ls | grep mongo_data

Puis :

docker compose stop mongo
docker run --rm -v VOTRE_VOLUME:/data:ro -v "$PWD":/backup alpine \
tar czf /backup/mongo-volume-$(date +%F).tar.gz -C /data .
docker compose start mongo

Plus rapide sur de gros volumes, 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, ou docker login non effectué
pull access deniedIdentifiants du registre absents ou expirés
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
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