Aller au contenu principal

Référence de configuration

sol2flow lit sa configuration dans des variables d’environnement ; avec Docker Compose, dans le fichier .env à côté de compose.yml. L’application les vérifie au démarrage et refuse de démarrer, avec un message clair, lorsqu’une variable obligatoire manque ou est invalide. Modifiez une valeur, puis appliquez-la avec docker compose up -d.

La plupart des réglages ne sont pas des variables d’environnement : les méthodes de connexion, l’inscription libre, les limites d’envoi de fichiers et les e-mails de notification se règlent dans l’application sous Admin (Paramètres de l’instance).

Les secrets comme AUTH_SECRET ou DATABASE_URL peuvent aussi venir de fichiers, par exemple de secrets Docker (Secrets depuis des fichiers).

Obligatoires​

VariableDescription
APP_URLL’adresse publique de l’instance, p. ex. https://flow.example.com. Utilisée pour les liens dans les e-mails, les redirections de connexion et les clés d’accès. Doit correspondre exactement à ce que les gens saisissent.
DATABASE_URLChaîne de connexion PostgreSQL, p. ex. postgresql://sol2flow:secret@db:5432/sol2flow?schema=public. PostgreSQL 16, 17 ou 18. Le compose.yml d’exemple la construit à partir de POSTGRES_PASSWORD.
AUTH_SECRETAu moins 16 caractères ; utilisez 32 octets aléatoires : openssl rand -base64 32. Sert de clé aux signatures internes (par exemple les compteurs anonymisés de limitation de débit).
APP_ENCRYPTION_KEYAu moins 32 caractères ; utilisez openssl rand -base64 32. Chiffre les secrets stockés dans la base de données. Ne la perdez jamais et ne la changez pas à la légère : voir changer la clé de chiffrement.

Général​

VariableDéfautDescription
LOG_LEVELinfotrace, debug, info, warn, error ou fatal. Les journaux sont des lignes JSON sur la sortie standard, et dans un fichier avec LOG_FILE (ci-dessous).
RUN_MIGRATIONStrueApplique les migrations de base de données au démarrage. Ne mettez false que si vous les exécutez autrement.
TRUST_PROXY_HOPS1Nombre de reverse proxies devant l’application qui ajoutent une entrée à X-Forwarded-For (1 à 10). Détermine quelle adresse est considérée comme celle du client. Voir Reverse proxy et TLS.
PUBLIC_WEBSITE_URLhttps://sol2flow.comL’adresse vers laquelle pointe la mention « Partagé avec sol2flow » en pied de page d’un lien public de tableau (http ou https ; s’ouvre dans un nouvel onglet, sans référent). Indiquez votre propre site, ou laissez vide pour afficher le texte sans lien.
ROLE(vide)web = ne pas exécuter les tâches de fond dans ce conteneur ; utilisé avec un conteneur worker séparé (Installation).

Connexions à la base de données​

Chaque conteneur de l'application garde deux pools de connexions PostgreSQL : un pour les pages, les actions et l'API, et un plus petit pour la file des tâches de fond. Leur taille vaut par conteneur :

VariableDéfautDescription
DATABASE_POOL_SIZE10 ; 5 avec ROLE=workerConnexions pour les pages, les actions, l'API et les tâches. Un connection_limit dans DATABASE_URL (ou dans le secret DATABASE_URL_FILE) a la priorité.
DATABASE_JOBS_POOL_SIZE5 ; 2 avec ROLE=web, qui ne fait que mettre des tâches en fileConnexions pour la file des tâches.

Quand un pool est plein, une requête attend une connexion libre (jusqu'à 10 secondes) au lieu d'en ouvrir une autre. Dans la base, les connexions apparaissent sous les noms sol2flow-web, sol2flow-worker ou sol2flow-all (un conteneur sans ROLE), celles de la file des tâches suivies de -jobs. Pour voir qui en tient combien :

SELECT application_name, state, count(*) FROM pg_stat_activity WHERE datname = 'sol2flow' GROUP BY 1, 2;

Tout ce qui se connecte compte dans le max_connections du serveur de base de données : 100 par défaut, dont 3 réservées aux superutilisateurs ; les autres bases du même serveur comptent aussi.

ConfigurationConnexions au plus
Un seul conteneur (sans ROLE), le compose.yml par défaut10 + 5 = 15
Un conteneur ROLE=web10 + 2 = 12 chacun
Un conteneur ROLE=worker5 + 5 = 10 chacun
Migrations au démarrage, outils en ligne de commande, sauvegardes, psql1 à 2 chacun pendant leur exécution
Une mise à jour progressive qui démarre d'abord le nouveau conteneurles conteneurs mis à jour en double, jusqu'à l'arrêt des anciens

Web et worker sur un même serveur utilisent 22 connexions, 44 au plus pendant le remplacement des deux, bien en dessous de la limite par défaut. Plus de conteneurs ou des pools plus grands s'additionnent : gardez le total, doublé si vos mises à jour démarrent les nouveaux conteneurs avant d'arrêter les anciens, sous max_connections moins 3. Si le journal affiche sorry, too many clients already, réduisez la taille des pools ou augmentez max_connections dans PostgreSQL.

Lieu de connexion​

sol2flow prévient chacun en cas de connexion inhabituelle : un nouvel appareil, un nouveau pays, ou une connexion depuis un autre pays peu après une connexion ailleurs. Pour le pays, il lui faut le lieu approximatif de chaque connexion. Sans source de lieu, les alertes fonctionnent quand même, mais comparent les appareils et les réseaux plutôt que les pays.

VariableDéfautDescription
GEO_HEADERS(vide)cloudflare = lire le lieu dans les en-têtes de Cloudflare (CF-IPCountry, cf-ipcity, cf-region). Uniquement si tout le trafic arrive à l’application par Cloudflare.
GEOIP_DB(vide)Chemin, dans le conteneur, d’un fichier MaxMind GeoLite2 City ou Country, ou DB-IP Lite (.mmdb), par exemple /data/geoip/GeoLite2-City.mmdb.

Si les deux sont définies, les en-têtes de Cloudflare l’emportent et le fichier sert pour les requêtes qui n’en ont pas. Seuls le pays et la ville sont conservés, jamais l’adresse IP : voir ce qui est conservé.

Derrière Cloudflare​

Si votre instance est derrière Cloudflare — en proxy (nuage orange) ou via un Cloudflare Tunnel —, Cloudflare peut ajouter le lieu à chaque requête :

  1. Dans le tableau de bord Cloudflare, ouvrez votre domaine et vérifiez que Network → IP Geolocation est activé (c’est le cas par défaut). Cela ajoute le pays (CF-IPCountry).
  2. Pour la ville aussi, allez dans Rules → Settings, trouvez Managed Transforms et activez Add visitor location headers. Cela ajoute notamment cf-ipcity et cf-region.
  3. Définissez GEO_HEADERS=cloudflare dans .env et appliquez-le avec docker compose up -d.
avertissement

Ne définissez GEO_HEADERS=cloudflare que si l’application n’est joignable par aucun autre chemin. N’importe qui peut envoyer un en-tête CF-IPCountry ; seul Cloudflare devant l’application garantit qu’il vient de Cloudflare. Avec un Tunnel, ne publiez pas le port de l’application sur l’hôte ; avec un enregistrement DNS en proxy, ne laissez passer que les adresses de Cloudflare dans votre pare-feu. Sans la variable, sol2flow ignore ces en-têtes.

Un Cloudflare Tunnel définit aussi X-Forwarded-For ; si rien d’autre n’est devant l’application, gardez TRUST_PROXY_HOPS=1.

Avec une base GeoIP​

Si vous n’êtes pas derrière Cloudflare, téléchargez une base GeoIP gratuite et indiquez-la dans GEOIP_DB. La recherche se fait dans l’application : aucune adresse n’est envoyée ailleurs. sol2flow ne fournit pas de base, à cause de leurs licences :

  • MaxMind GeoLite2 (City ou Country) : créez un compte MaxMind gratuit, acceptez la licence GeoLite2 (EULA) et créez une clé de licence. La licence demande de garder les données à jour et de citer MaxMind là où vous les affichez. Le plus simple pour rester à jour est l’outil geoipupdate de MaxMind, par exemple comme service dans votre compose.yml :

    services:
    geoipupdate:
    image: ghcr.io/maxmind/geoipupdate
    restart: unless-stopped
    environment:
    GEOIPUPDATE_ACCOUNT_ID: '123456'
    GEOIPUPDATE_LICENSE_KEY: '<votre clé de licence>'
    GEOIPUPDATE_EDITION_IDS: GeoLite2-City
    GEOIPUPDATE_FREQUENCY: '72' # heures
    volumes: ['./geoip:/usr/share/GeoIP']
  • DB-IP Lite (« IP to City Lite », format MMDB) : à télécharger chaque mois sur db-ip.com. Elle est sous licence CC BY 4.0, qui demande de citer DB-IP.

Montez ensuite le dossier dans l’application et indiquez le chemin :

services:
app:
volumes: ['./geoip:/data/geoip:ro']
GEOIP_DB=/data/geoip/GeoLite2-City.mmdb

L’application lit le fichier à la première connexion et remarque une version plus récente en moins de 10 minutes, sans redémarrage. Si le fichier manque ou est illisible, elle écrit un avertissement dans le journal et continue sans lieux.

Ce qui est conservé​

Pour chaque personne, sol2flow retient les appareils et lieux depuis lesquels elle s’est connectée : la famille de navigateur et de système (par exemple « Chrome », « macOS »), le pays et la ville, et une empreinte à clé du réseau (les trois premières parties d’une adresse IPv4, ou les trois premiers groupes d’une adresse IPv6). Ni adresse IP, ni identification complète du navigateur. Les entrées inutilisées depuis 180 jours sont supprimées automatiquement, et toutes disparaissent avec le compte. Les alertes affichent l’adresse IP en partie masquée (203.0.x.x).

Journaux et suivi des erreurs​

L’application écrit des lignes JSON sur la sortie standard (docker compose logs app). Pour les conserver aussi dans des fichiers, définissez LOG_FILE : l’application écrit à côté de ce chemin des fichiers datés et en commence de nouveaux toute seule :

VariableDéfautDescription
LOG_FILE(vide)Où vont les fichiers de journal et comment ils s’appellent, p. ex. /data/logs/app.log ; la sortie standard reçoit toujours les lignes. Vide = sortie standard uniquement.
LOG_FILE_FREQUENCYdailyUn nouveau fichier chaque jour (daily, app-2026-09-30.log) ou chaque heure (hourly, app-2026-09-30-14.log), en UTC ; none = pas de date dans le nom (app.log).
LOG_FILE_MAX_SIZE(vide)Vide = pas de limite de taille. Avec une taille comme 20m (k, m ou g), un fichier plein continue dans app-2026-09-30.1.log, puis .2.log, etc.
LOG_FILE_MAX_FILES10Nombre de fichiers de journal de ce nom conservés, les plus récents ; les plus anciens sont supprimés. Par jour et sans limite de taille, cela fait dix jours. 0 = tout garder.

Avec LOG_FILE=/data/logs/app.log, le répertoire contient par exemple :

app-2026-09-29.log
app-2026-09-30.log
app-2026-09-30.1.log # seulement avec LOG_FILE_MAX_SIZE : le deuxième fichier du jour
app.log -> app-2026-09-30.1.log
  • Les jours sont des jours UTC : le nouveau fichier commence à minuit UTC, pas à minuit heure locale.
  • app.log pointe toujours vers le fichier courant (un lien symbolique) : tail -F /data/logs/app.log suit donc le journal d’un fichier à l’autre. Utilisez -F, pas -f : tail -f reste sur le fichier ouvert en premier. Avec LOG_FILE_FREQUENCY=none et sans limite de taille, app.log est le fichier lui-même ; avec une limite, les fichiers pleins deviennent app.1.log, app.2.log… (plus grand = plus récent).
  • Un redémarrage continue le fichier du jour (le plus récent de la journée) ; rien n’est écrasé. Seuls les fichiers de ce nom sont supprimés : ceux de l’ancien nommage (app.log.1…) et les autres fichiers du répertoire restent.
  • Un nom de fichier par conteneur. Deux processus ne doivent jamais écrire le même LOG_FILE : si un worker séparé partage le répertoire des journaux, utilisez p. ex. /data/logs/web.log pour l’application et /data/logs/worker.log pour le worker.

L’image contient un répertoire /data/logs prévu pour cela : montez-y un volume, comme dans l’exemple. Si les fichiers ne peuvent pas être écrits (mauvais chemin, droit manquant, disque plein), l’application consigne un avertissement et continue d’écrire sur la sortie standard ; la journalisation dans des fichiers ne l’arrête jamais.

Le suivi des erreurs envoie les erreurs à Sentry ou à un service qui parle son protocole, comme Bugsink ou GlitchTip auto-hébergés. Il reste désactivé tant que vous ne définissez pas de DSN :

VariableDéfautDescription
SENTRY_DSN(vide)Le DSN de votre projet (https://<key>@<host>/<project>). Vide = désactivé : rien n’est chargé ni envoyé. Depuis un fichier : SENTRY_DSN_FILE.
SENTRY_ENVIRONMENTproductionL’environnement sous lequel les erreurs sont classées, p. ex. staging.
SENTRY_RELEASEla version de l’imageLa version à laquelle appartiennent les erreurs ; par défaut la version de l’image, p. ex. 1.4.0.
SENTRY_TRACES_SAMPLE_RATE0Part des requêtes tracées pour mesurer les performances, de 0 à 1. 0 = erreurs uniquement.
SENTRY_LOGSfalsetrue envoie aussi les entrées de journal de niveau warn et plus aux Logs de Sentry. Bugsink et GlitchTip n’ont pas de journaux : là, cela n’a aucun effet.

Ce qui est envoyé : les erreurs côté serveur (requêtes en échec, erreurs que l’application consigne, tâches de fond en échec, marquées du nom de la tâche) et dans le navigateur (erreurs sur les pages, y compris celles derrière la page « Un problème est survenu »). Ce qui ne l’est pas : aucun enregistrement de session, aucun nom, adresse e-mail ni adresse IP, aucun cookie, corps de requête ni paramètre d’URL ; les jetons présents dans les adresses (liens publics de tableau, invitations, réinitialisations de mot de passe) sont retirés. Le navigateur reçoit le DSN depuis la page : un changement ne demande qu’un redémarrage de l’application.

Par exemple avec Bugsink : lancez-le à côté de sol2flow derrière votre reverse proxy (ici sur errors.example.com), connectez-vous, créez une équipe et un projet, puis copiez le DSN du projet dans .env :

docker run -d --name bugsink --restart unless-stopped -p 127.0.0.1:8000:8000 -v bugsink:/data \
-e SECRET_KEY="$(openssl rand -base64 50)" -e CREATE_SUPERUSER=[email protected]:<a password> \
-e BASE_URL=https://errors.example.com -e BEHIND_HTTPS_PROXY=true bugsink/bugsink
SENTRY_DSN=https://<key>@errors.example.com/1

Les navigateurs envoient eux aussi leurs erreurs à l’adresse du DSN : elle doit donc être joignable depuis l’extérieur, en HTTPS.

Stockage​

VariableDéfautDescription
STORAGE_DRIVERlocallocal (un répertoire, montez un volume) ou s3 (tout stockage objet compatible S3).
STORAGE_LOCAL_DIR/data/uploadsEmplacement des fichiers avec le driver local.
S3_ENDPOINT(vide)Adresse de l’API utilisée par le serveur, p. ex. https://s3.eu-central-1.amazonaws.com ou https://<account>.r2.cloudflarestorage.com. Vide = AWS pour S3_REGION.
S3_PUBLIC_ENDPOINT= S3_ENDPOINTAdresse de l’API utilisée par les navigateurs pour les liens de téléchargement, lorsqu’elle diffère de celle du serveur (nom d’hôte interne, réseau privé).
S3_REGIONus-east-1La région du bucket (Cloudflare R2 : auto).
S3_BUCKETsol2flowLe bucket ; créez-le au préalable.
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY(vide)Identifiants. Vide = la chaîne par défaut du SDK (par exemple un rôle IAM).
S3_FORCE_PATH_STYLEtruetrue pour https://host/bucket/key (la plupart des stockages auto-hébergés), false pour https://bucket.host/key.
MAX_UPLOAD_MB10Hérité. Les limites d’envoi se règlent dans Admin → Général → Envoi de fichiers ; cette variable ne dimensionne que les requêtes de formulaire internes.

Détails et exemples : Stockage.

E-mail​

VariableDéfautDescription
MAIL_DRIVERlogsmtp pour envoyer des e-mails ; log écrit les messages dans le journal de l’application à la place (tests uniquement).
MAIL_FROMsol2flow <[email protected]>Nom et adresse de l’expéditeur.
SMTP_HOSTlocalhostServeur SMTP.
SMTP_PORT1025Généralement 587 (STARTTLS) ou 465 (TLS).
SMTP_USER, SMTP_PASSWORD(vide)Identifiants, si le serveur en demande.
SMTP_SECUREfalsetrue pour le TLS implicite (port 465) ; false pour STARTTLS ou une connexion non chiffrée.

Détails : E-mail.

Premier administrateur​

Utilisées uniquement lors d’un démarrage où l’instance n’a aucun utilisateur ; ignorées ensuite.

VariableDéfautDescription
BOOTSTRAP_ADMIN_EMAIL(vide)E-mail du premier membre du staff, le Propriétaire. Facultatif.
BOOTSTRAP_ADMIN_USERNAMEadminSon nom d’utilisateur.
BOOTSTRAP_ADMIN_PASSWORD(vide)Son mot de passe. Le compte n’est créé que s’il est défini ; laissez-le vide pour créer le Propriétaire en ligne de commande à la place (Installation).

Secrets depuis des fichiers (secrets Docker)​

Chaque secret peut aussi être lu depuis un fichier plutôt que depuis l’environnement, comme le fait l’image PostgreSQL officielle : définissez X_FILE avec le chemin d’un fichier qui contient la valeur de X. C’est ainsi que les secrets Docker Swarm et Compose arrivent dans un conteneur : sous forme de fichiers dans /run/secrets/.

VariableDepuis un fichier
AUTH_SECRETAUTH_SECRET_FILE
APP_ENCRYPTION_KEYAPP_ENCRYPTION_KEY_FILE
DATABASE_URLDATABASE_URL_FILE
S3_ACCESS_KEY_IDS3_ACCESS_KEY_ID_FILE
S3_SECRET_ACCESS_KEYS3_SECRET_ACCESS_KEY_FILE
SMTP_USERSMTP_USER_FILE
SMTP_PASSWORDSMTP_PASSWORD_FILE
BOOTSTRAP_ADMIN_PASSWORDBOOTSTRAP_ADMIN_PASSWORD_FILE
SENTRY_DSNSENTRY_DSN_FILE

Les règles :

  • Tout le fichier est la valeur, sauf un saut de ligne final (LF ou CRLF), que docker secret create et la plupart des éditeurs ajoutent. Les espaces au début ou à la fin font partie de la valeur.
  • Définissez l’un ou l’autre. Si X et X_FILE sont tous deux définis, l’application refuse de démarrer et nomme la variable. Elle refuse aussi de démarrer si le fichier manque ou ne peut pas être lu. Une variable vide compte comme non définie.
  • Les valeurs n’apparaissent jamais dans le journal, seulement les noms de variables et les chemins.
  • Le fichier doit être lisible par l’utilisateur du conteneur, qui n’est pas root (uid 1001). Les secrets Swarm le sont (mode 0444 par défaut) ; pour un fichier que vous montez vous-même, vérifiez ses droits.
  • DATABASE_URL_FILE contient la chaîne de connexion entière, pas seulement le mot de passe. Un mot de passe avec des caractères spéciaux (@, :, /, %, …) doit y être encodé pour une URL ; ou utilisez un mot de passe hexadécimal comme ci-dessous.

Exemple pour Docker Swarm, où PostgreSQL lit lui aussi son mot de passe depuis un secret. Créez les secrets une fois :

DB_PASSWORD=$(openssl rand -hex 16)
printf '%s' "$DB_PASSWORD" | docker secret create sol2flow_db_password -
printf 'postgresql://sol2flow:%s@db:5432/sol2flow?schema=public' "$DB_PASSWORD" | docker secret create sol2flow_database_url -
unset DB_PASSWORD
openssl rand -base64 32 | docker secret create sol2flow_auth_secret -
openssl rand -base64 32 | docker secret create sol2flow_app_encryption_key -
openssl rand -hex 12 | tee /dev/stderr | docker secret create sol2flow_admin_password - # affiche le mot de passe une fois

Gardez une copie de la clé de chiffrement en lieu sûr, hors du swarm : un secret ne peut pas être relu (pourquoi c’est important). Puis un fichier de stack, stack.yml :

services:
app:
image: ghcr.io/sol2flow/sol2flow:latest # fixez une version en production
environment:
APP_URL: https://flow.example.com
AUTH_SECRET_FILE: /run/secrets/sol2flow_auth_secret
APP_ENCRYPTION_KEY_FILE: /run/secrets/sol2flow_app_encryption_key
DATABASE_URL_FILE: /run/secrets/sol2flow_database_url
BOOTSTRAP_ADMIN_PASSWORD_FILE: /run/secrets/sol2flow_admin_password
LOG_FILE: /data/logs/app.log # facultatif : fichiers app-AAAA-MM-JJ.log, app.log → le fichier courant
secrets: [sol2flow_auth_secret, sol2flow_app_encryption_key, sol2flow_database_url, sol2flow_admin_password]
ports: ['3000:3000']
volumes: [uploads:/data/uploads, logs:/data/logs]
db:
image: postgres:18-alpine
environment:
POSTGRES_USER: sol2flow
POSTGRES_DB: sol2flow
POSTGRES_PASSWORD_FILE: /run/secrets/sol2flow_db_password
secrets: [sol2flow_db_password]
volumes: [pgdata:/var/lib/postgresql] # from PostgreSQL 18 not …/data (data: …/18/docker)
secrets:
sol2flow_auth_secret: { external: true }
sol2flow_app_encryption_key: { external: true }
sol2flow_database_url: { external: true }
sol2flow_db_password: { external: true }
sol2flow_admin_password: { external: true }
volumes: { pgdata: {}, uploads: {}, logs: {} }

Déployez-le avec docker stack deploy -c stack.yml sol2flow et connectez-vous en tant que admin avec le mot de passe affiché. Docker Compose seul fonctionne de la même façon avec des secrets à base de fichiers (secrets: { sol2flow_auth_secret: { file: ./auth_secret } }).

Fichier Compose uniquement​

Ces variables sont lues par le compose.yml d’exemple, pas par l’application :

VariableDéfautDescription
SOL2FLOW_VERSIONlatestLe tag de l’image à exécuter. Fixez une version en production.
POSTGRES_PASSWORDsol2flowMot de passe du PostgreSQL fourni. Choisissez-en un aléatoire.

SOL2FLOW_VERSION dans .env arrive aussi dans le conteneur, où c’est la version que signale le suivi des erreurs. Fixez une version complète comme 1.4.0 plutôt que latest.

Pas pour la production

L’application connaît aussi quelques réglages qui n’existent que pour le développement et les tests automatisés. Ne définissez dans un .env de production rien qui ne figure pas sur cette page.