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
| Variable | Description |
|---|---|
APP_URL | L’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_URL | Chaî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_SECRET | Au 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_KEY | Au 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
| Variable | Défaut | Description |
|---|---|---|
LOG_LEVEL | info | trace, 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_MIGRATIONS | true | Applique les migrations de base de données au démarrage. Ne mettez false que si vous les exécutez autrement. |
TRUST_PROXY_HOPS | 1 | Nombre 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_URL | https://sol2flow.com | L’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 :
| Variable | Défaut | Description |
|---|---|---|
DATABASE_POOL_SIZE | 10 ; 5 avec ROLE=worker | Connexions 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_SIZE | 5 ; 2 avec ROLE=web, qui ne fait que mettre des tâches en file | Connexions 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.
| Configuration | Connexions au plus |
|---|---|
Un seul conteneur (sans ROLE), le compose.yml par défaut | 10 + 5 = 15 |
Un conteneur ROLE=web | 10 + 2 = 12 chacun |
Un conteneur ROLE=worker | 5 + 5 = 10 chacun |
Migrations au démarrage, outils en ligne de commande, sauvegardes, psql | 1 à 2 chacun pendant leur exécution |
| Une mise à jour progressive qui démarre d'abord le nouveau conteneur | les 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.
| Variable | Défaut | Description |
|---|---|---|
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 :
- 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). - Pour la ville aussi, allez dans Rules → Settings, trouvez Managed Transforms et activez Add visitor
location headers. Cela ajoute notamment
cf-ipcityetcf-region. - Définissez
GEO_HEADERS=cloudflaredans.envet appliquez-le avecdocker compose up -d.
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
geoipupdatede MaxMind, par exemple comme service dans votrecompose.yml:services:geoipupdate:image: ghcr.io/maxmind/geoipupdaterestart: unless-stoppedenvironment:GEOIPUPDATE_ACCOUNT_ID: '123456'GEOIPUPDATE_LICENSE_KEY: '<votre clé de licence>'GEOIPUPDATE_EDITION_IDS: GeoLite2-CityGEOIPUPDATE_FREQUENCY: '72' # heuresvolumes: ['./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 :
| Variable | Défaut | Description |
|---|---|---|
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_FREQUENCY | daily | Un 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_FILES | 10 | Nombre 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.logpointe toujours vers le fichier courant (un lien symbolique) :tail -F /data/logs/app.logsuit donc le journal d’un fichier à l’autre. Utilisez-F, pas-f:tail -freste sur le fichier ouvert en premier. AvecLOG_FILE_FREQUENCY=noneet sans limite de taille,app.logest le fichier lui-même ; avec une limite, les fichiers pleins deviennentapp.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.logpour l’application et/data/logs/worker.logpour 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 :
| Variable | Défaut | Description |
|---|---|---|
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_ENVIRONMENT | production | L’environnement sous lequel les erreurs sont classées, p. ex. staging. |
SENTRY_RELEASE | la version de l’image | La version à laquelle appartiennent les erreurs ; par défaut la version de l’image, p. ex. 1.4.0. |
SENTRY_TRACES_SAMPLE_RATE | 0 | Part des requêtes tracées pour mesurer les performances, de 0 à 1. 0 = erreurs uniquement. |
SENTRY_LOGS | false | true 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 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
| Variable | Défaut | Description |
|---|---|---|
STORAGE_DRIVER | local | local (un répertoire, montez un volume) ou s3 (tout stockage objet compatible S3). |
STORAGE_LOCAL_DIR | /data/uploads | Emplacement 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_ENDPOINT | Adresse 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_REGION | us-east-1 | La région du bucket (Cloudflare R2 : auto). |
S3_BUCKET | sol2flow | Le 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_STYLE | true | true pour https://host/bucket/key (la plupart des stockages auto-hébergés), false pour https://bucket.host/key. |
MAX_UPLOAD_MB | 10 | Hé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
| Variable | Défaut | Description |
|---|---|---|
MAIL_DRIVER | log | smtp pour envoyer des e-mails ; log écrit les messages dans le journal de l’application à la place (tests uniquement). |
MAIL_FROM | sol2flow <[email protected]> | Nom et adresse de l’expéditeur. |
SMTP_HOST | localhost | Serveur SMTP. |
SMTP_PORT | 1025 | Généralement 587 (STARTTLS) ou 465 (TLS). |
SMTP_USER, SMTP_PASSWORD | (vide) | Identifiants, si le serveur en demande. |
SMTP_SECURE | false | true 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.
| Variable | Défaut | Description |
|---|---|---|
BOOTSTRAP_ADMIN_EMAIL | (vide) | E-mail du premier membre du staff, le Propriétaire. Facultatif. |
BOOTSTRAP_ADMIN_USERNAME | admin | Son 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/.
| Variable | Depuis un fichier |
|---|---|
AUTH_SECRET | AUTH_SECRET_FILE |
APP_ENCRYPTION_KEY | APP_ENCRYPTION_KEY_FILE |
DATABASE_URL | DATABASE_URL_FILE |
S3_ACCESS_KEY_ID | S3_ACCESS_KEY_ID_FILE |
S3_SECRET_ACCESS_KEY | S3_SECRET_ACCESS_KEY_FILE |
SMTP_USER | SMTP_USER_FILE |
SMTP_PASSWORD | SMTP_PASSWORD_FILE |
BOOTSTRAP_ADMIN_PASSWORD | BOOTSTRAP_ADMIN_PASSWORD_FILE |
SENTRY_DSN | SENTRY_DSN_FILE |
Les règles :
- Tout le fichier est la valeur, sauf un saut de ligne final (LF ou CRLF), que
docker secret createet 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
XetX_FILEsont 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 (mode0444par défaut) ; pour un fichier que vous montez vous-même, vérifiez ses droits. DATABASE_URL_FILEcontient 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 :
| Variable | Défaut | Description |
|---|---|---|
SOL2FLOW_VERSION | latest | Le tag de l’image à exécuter. Fixez une version en production. |
POSTGRES_PASSWORD | sol2flow | Mot 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.
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.