Aller au contenu

Variables d'environnement

Référence des variables déclarées dans .env à la racine du dépôt. .env ne contient que des valeurs par défaut non secrètes et est versionné.

Gestion des secrets

  • Les credentials réels (mots de passe, clés API, tokens, DSN avec mot de passe) vivent dans .env.local, non versionné (présent dans .gitignore).
  • En production, les secrets sont injectés via les variables d'environnement du docker-compose.prod.yml ou un gestionnaire externe. Ils ne transitent jamais par Git.
  • En cas de fuite : changer immédiatement la valeur dans tous les environnements avant de chercher la cause.

Symfony charge les variables dans l'ordre suivant : .env.env.local → variables d'environnement du process. La dernière valeur lue gagne.

À surcharger obligatoirement en production

Les valeurs présentes dans .env sont des valeurs factices destinées au seul environnement de développement local. Elles ne doivent jamais être utilisées en production. Avant tout déploiement, vérifier que chacune des variables ci-dessous est définie via compose.prod.yaml ou un gestionnaire de secrets externe :

Variable Risque si non surchargée
APP_SECRET Signature CSRF et cookies prévisibles → forge de session possible.
POSTGRES_PASSWORD Mot de passe !ChangeMe! connu publiquement (versionné) → accès BDD trivial.
DATABASE_URL Contient POSTGRES_PASSWORD — recomposer ou réutiliser le DSN avec le nouveau mot de passe.
RABBITMQ_USER / RABBITMQ_PASSWORD Couple guest/guest par défaut → prise de contrôle du bus de messages.
MESSENGER_TRANSPORT_DSN Contient les credentials RabbitMQ — à recomposer en cohérence.
CADDY_MERCURE_JWT_SECRET Secret connu → forge de JWT Mercure (publication arbitraire d'événements SSE).
TYPESENSE_API_KEY Clé xyz-dev-key connue → lecture/écriture arbitraire dans l'index de recherche.
TRUSTED_HOSTS Regex permissive (localhost, 127.0.0.1…) → Host: spoofing possible. À restreindre au domaine cible.
SERVER_NAME localhost par défaut → certificats Caddy invalides en prod. À fixer au domaine réel.
SENTRY_DSN Vide → aucune télémétrie d'erreurs en prod. À renseigner.
GRAYLOG_HOST Vide → aucun log centralisé en prod. À renseigner.

Règle absolue

Aucune valeur secrète ne transite par Git. Si l'une des valeurs ci-dessus est jamais commitée par erreur (même brièvement), la considérer comme compromise et la régénérer dans tous les environnements avant de chercher la cause de la fuite.

Framework Symfony

Variable Défaut Rôle
APP_ENV dev Environnement applicatif (dev, test, prod).
APP_SECRET !ChangeMe! Secret utilisé pour la signature CSRF, les cookies, etc. À surcharger en prod.
TRUSTED_PROXIES 127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 Liste CIDR des proxies de confiance. Définie inline dans compose.yaml pour Docker networking — ne pas redéfinir sauf en prod derrière un proxy personnalisé.
TRUSTED_HOSTS ^(kirexo\.com\|kirexo\.co\|kirexo\.app\|localhost\|127\.0\.0\.1)$ Expression régulière des Host autorisés. Définie inline dans compose.yaml. À surcharger en prod pour n'autoriser que votre domaine.
DEFAULT_URI https://localhost URI de base pour la génération d'URL dans les contextes non-HTTP (commandes Symfony Console, Messenger workers).

FrankenPHP / Caddy

Variable Défaut Rôle
SERVER_NAME localhost Nom de domaine(s) sur lequel Caddy écoute. FrankenPHP le lit au démarrage du conteneur. En prod, définir la valeur exacte du domaine (ex. www.kirexo.fr). Utilisé aussi comme label Traefik dans compose.prod.yaml.
CADDY_MERCURE_JWT_SECRET !ChangeThisMercureHubJWTSecretKey! Secret utilisé pour signer et vérifier les JWT Mercure (SSE). En dev, la valeur par défaut est suffisante — À surcharger en prod par une valeur aléatoire forte. Dérive les variables MERCURE_PUBLISHER_JWT_KEY et MERCURE_SUBSCRIBER_JWT_KEY exposées au conteneur.

Doctrine / PostgreSQL

Variable Défaut Rôle
POSTGRES_DB app Nom de la base.
POSTGRES_USER app Utilisateur PostgreSQL.
POSTGRES_PASSWORD !ChangeMe! Mot de passe PostgreSQL. À surcharger en prod.
POSTGRES_VERSION 16 Version de Postgres (utilisée par Doctrine et l'image du conteneur).
DATABASE_URL postgresql://app:!ChangeMe!@database:5432/app?serverVersion=16&charset=utf8 DSN Doctrine complet. Recomposé à partir des variables ci-dessus.

Messenger / RabbitMQ

Variable Défaut Rôle
RABBITMQ_USER guest Utilisateur RabbitMQ.
RABBITMQ_PASSWORD guest Mot de passe RabbitMQ. À surcharger en prod.
MESSENGER_TRANSPORT_DSN amqp://guest:guest@rabbitmq:5672/%2f/messages DSN du transport Messenger principal (bus de commandes async).

Redis

Variable Défaut Rôle
REDIS_URL redis://redis:6379 URL de connexion Redis. Utilisée par : le pool de cache applicatif (config/packages/cache.yaml), le handler de session HTTP (config/packages/framework.yaml) et — par défaut en dev — le store de verrouillage Symfony Lock (cf. LOCK_DSN).

Typesense

Variable Défaut Rôle
TYPESENSE_HOST typesense Hôte du moteur de recherche Typesense.
TYPESENSE_PORT 8108 Port Typesense.
TYPESENSE_API_KEY xyz-dev-key Clé API Typesense. À surcharger en prod.

Gotenberg

Variable Défaut Rôle
GOTENBERG_URL http://gotenberg:3000 URL du service Gotenberg (génération PDF).

Mailer

Variable Défaut Rôle
MAILER_DSN smtp://mailer:1025 DSN du mailer Symfony. En dev, pointe sur Mailpit (aucun mail ne sort de la machine).

Symfony Lock

Variable Défaut Rôle
LOCK_DSN redis://redis:6379 DSN du store de verrouillage distribué. Redis est utilisé par défaut pour que les verrous soient partagés entre le serveur web FrankenPHP et les workers Messenger — flock (lock fichier système) ne fonctionnerait qu'au sein d'un seul processus / filesystem. En prod multi-instance, le même DSN convient ; pour une dépendance moindre on peut basculer sur postgresql+advisory://….

Observabilité

Variable Défaut Rôle
SENTRY_DSN (vide) DSN Sentry. Vide en dev, injecté en prod.
GRAYLOG_HOST (vide) Hôte Graylog pour les logs GELF/UDP. Vide en dev — aucun service Graylog local n'est démarré. En prod, pointer vers votre instance Graylog.
GRAYLOG_PORT 12201 Port Graylog (GELF UDP).

Graylog — prod uniquement

Le handler Monolog gelf n'est activé que sous when@prod (voir config/packages/monolog.yaml). En dev, les logs partent uniquement dans les fichiers Monolog / la console Symfony. Si GRAYLOG_HOST est vide en prod, le handler UDP échoue silencieusement — Monolog capture l'erreur de transport sans interrompre l'application.

Le handler requiert le package PHP graylog2/gelf-php :

castor composer "require graylog2/gelf-php:^2.0"

Image de production et préfixe de registry

Ces deux variables pilotent quelle image Docker la production tire du registry. Elles ne vivent pas dans .env : en production, on les exporte dans le shell (ou un gestionnaire de secrets/config) avant docker compose pull && up.

Depuis l'étape 4, le service php de compose.prod.yaml ne build plus l'image sur le serveur : il PULL une image immuable poussée par la CI (job docker:build-prod, cf. Pipeline GitLab CI). La référence d'image est :

${IMAGES_PREFIX:-registry.gitlab.com/shaurifr/kirexo/}kirexo-prod:${KIREXO_VERSION:-latest}
Variable Défaut dans compose.yaml Défaut dans compose.prod.yaml Rôle
IMAGES_PREFIX (vide) registry.gitlab.com/shaurifr/kirexo/ Préfixe du nom de l'image Docker. En dev (compose.yaml), il préfixe kirexo-app (vide par défaut → image locale non poussée). En prod (compose.prod.yaml), il pointe le registry du projet devant kirexo-prod.
KIREXO_VERSION latest Tag de l'image kirexo-prod à déployer. Épingle une release précise (le tag Git YYYYMMDDHHMM que la CI pousse aussi comme tag d'image).

IMAGES_PREFIX doit finir par / si vous le définissez manuellement

Le préfixe est concaténé directement au nom de l'image (${IMAGES_PREFIX}kirexo-prod). S'il ne se termine pas par une barre oblique, le préfixe se colle au nom : registry.gitlab.com/shaurifr/kirexokirexo-prod. Le défaut prod (registry.gitlab.com/shaurifr/kirexo/) inclut déjà le / final — ne le redéfinissez que si vous poussez vers un autre registry, en conservant le / terminal.

Toujours exporter KIREXO_VERSION en production

Le défaut latest est un tag mutable : il est réécrit à chaque release. Si le serveur de prod ne définit pas KIREXO_VERSION, un docker compose pull ramène la dernière image publiée — vous perdez la possibilité de rejouer une version précise ou de revenir en arrière de façon déterministe.

Le déploiement DOIT donc exporter KIREXO_VERSION=<tag> (un tag Git YYYYMMDDHHMM) avant docker compose pull && up, pour épingler exactement l'image testée par la CI et garder un rollback propre. La procédure complète est décrite dans Déployer Kirexo en production.

Variables Docker Compose (dev uniquement)

Le fichier compose.override.yaml expose également des variables pour surcharger les ports locaux quand ils entrent en conflit avec d'autres services. Elles ne vivent pas dans .env : définissez-les dans votre shell ou dans un .env.local si vous en avez besoin.

Modèle .env.local.dist

Un modèle versionné .env.local.dist à la racine du dépôt regroupe les variables surchargeables (ports Compose, USER_ID/GROUP_ID du build, Xdebug, observabilité, devcontainer). Pour démarrer : cp .env.local.dist .env.local puis décommentez uniquement les lignes que vous voulez surcharger. La liste exhaustive et le rôle de chaque variable restent documentés ci-dessous.

Variable Défaut Rôle
HTTP_PORT 80 Port HTTP de FrankenPHP côté hôte.
HTTPS_PORT 443 Port HTTPS (TCP) de FrankenPHP côté hôte.
HTTP3_PORT 443 Port HTTP/3 (UDP) de FrankenPHP côté hôte.
POSTGRES_PORT 5432 Port PostgreSQL côté hôte. Permet de connecter un client BDD (DBeaver, DataGrip…) depuis l'hôte sans passer par docker compose port.
RABBITMQ_PORT 5672 Port AMQP de RabbitMQ côté hôte.
RABBITMQ_MANAGEMENT_PORT 15672 Port de l'UI RabbitMQ côté hôte.
TYPESENSE_PORT 8108 Port Typesense côté hôte.
GOTENBERG_PORT 3000 Port Gotenberg côté hôte.
MKDOCS_PORT 8000 Port du service docs (mkdocs-material) côté hôte.
MAILPIT_SMTP_PORT 1025 Port SMTP Mailpit côté hôte.
MAILPIT_UI_PORT 8025 Port de l'UI Mailpit côté hôte.

IMAGES_PREFIX n'est plus dev-only

IMAGES_PREFIX est lue par compose.yaml et par compose.prod.yaml. Comme son rôle et son défaut diffèrent entre les deux, elle est documentée dans sa propre section : Image de production et préfixe de registry.

Mode Xdebug

Xdebug est désactivé par défaut en dev via xdebug.mode = off dans frankenphp/conf.d/20-app.dev.ini — pas via une variable d'environnement. Pour activer ponctuellement la couverture, castor test:coverage injecte XDEBUG_MODE=coverage le temps du run (cf. castor.php). Les outils tiers comme PHPStorm peuvent forcer un mode différent à la volée via le flag CLI -dxdebug.mode=..., qui surcharge la directive INI le temps du processus.

Fuseau horaire du conteneur

Variable Défaut Rôle
TZ Europe/Paris en dev, (non définie) en prod Fuseau horaire du shell du conteneur php. Définie dans compose.override.yaml (chargé automatiquement en dev) sous la forme TZ: ${TZ:-Europe/Paris} — surchargeable via une variable d'environnement de l'hôte avant docker compose up. compose.prod.yaml ne la définit pas : la prod reste donc volontairement en UTC.

Portée limitée au shell — PHP reste figé sur UTC

TZ n'affecte que le shell et les outils non-PHP du conteneur (statusline Claude Code, commande date, logs Docker, scripts Bash). PHP ignore cette variable car date.timezone = UTC est figé dans frankenphp/conf.d/10-app.ini et a priorité.

Conséquence pratique : dans une même session, date côté shell renvoie Europe/Paris (ex. 20:22 CEST) tandis que php -r 'echo date("H:i e");' renvoie 18:22 UTC. C'est attendu, pas un bug. Le décalage de 2 h ci-dessus est l'heure d'été ; en hiver ce sera 1 h.

Côté Doctrine, la discipline qui en découle est classique en Symfony : stocker les DateTimeImmutable en UTC, convertir vers le fuseau de l'utilisateur au moment du rendu uniquement (filtres Twig, formatters). Ne jamais laisser une comparaison de dates s'appuyer sur le fuseau du shell.

Surcharger ponctuellement

Pour exécuter la stack dans un autre fuseau (ex. test d'un bug lié au passage à l'heure d'hiver côté UI) :

TZ=America/New_York docker compose up -d

Ou pour rendre la surcharge persistante côté hôte, déclarer TZ dans .env.local (lu par Docker Compose au démarrage).

Variables de build Docker (dev uniquement)

Ces variables sont lues par compose.override.yaml comme build.args du build du service php (image kirexo-dev). Elles réalignent l'utilisateur app du conteneur sur l'UID/GID de l'hôte pour que les fichiers créés dans le bind-mount /app (vendor/, var/, composer.lock…) appartiennent au développeur et non à root.

Variable Défaut Rôle
USER_ID 1000 (dans .env) UID de l'utilisateur hôte passé au build.
GROUP_ID 1000 (dans .env) GID correspondant, même raison.

Docker Compose ne lit que .env, jamais .env.local

Contrairement à Symfony (qui applique la cascade .env.env.local → environnement du process), Docker Compose ne lit qu'un seul fichier dotenv : .env. La cascade .env.local est une mécanique propre à Symfony. Définir USER_ID/GROUP_ID dans .env.local n'a donc aucun effet sur le build du devcontainer — utilisez .env (défaut versionné) ou une vraie variable d'environnement (surcharge ponctuelle).

C'est pourquoi .env (versionné) porte des défauts USER_ID=1000 / GROUP_ID=1000 dans sa section ###> build Docker (dev uniquement) ### : ils garantissent que tout lanceur trouve toujours les variables, quel que soit le point d'entrée (Castor, IDE JetBrains/Gateway, devcontainer CLI). Sans ce défaut, un lanceur hors Castor interpolait ${USER_ID}/${GROUP_ID} à vide et le build plantait sur groupmod: invalid group ID ''.

Précédence des valeurs UID/GID

Pour le user app du conteneur — et donc la propriété des fichiers du bind-mount /app — l'UID/GID résulte, par ordre de précédence décroissante :

  1. Variable d'environnement réelle présente au moment du docker compose (la plus prioritaire) :
    • Castor l'injecte automatiquement via posix_getuid() / posix_getgid() (fonction host_user_env() de castor.php) → castor up --build donne toujours vos vraies valeurs, sans rien configurer.
    • IDE JetBrains / Gateway (qui lance docker compose directement, hors Castor) : la variable doit être exportée dans l'environnement de la session qui démarre l'IDE — voir Ouvrir Kirexo dans un Dev Container PHPStorm.
  2. Défaut 1000:1000 versionné dans .env (lu par Docker Compose).
  3. Repli :-1000 du bloc args: de compose.override.yaml (filet si .env était absent).

En complément, dans le dockerfile_inline de compose.override.yaml, les références sont écrites $${USER_ID} / $${GROUP_ID} : le $$ échappe l'interpolation Compose pour que ce soit le moteur de build (via les ARG, repli :-1000) qui les résolve. C'est un filet anti-crash supplémentaire si l'environnement du process est vide.

Cas concret : GID hôte ≠ 1000

La plupart des utilisateurs Linux ont UID et GID à 1000 — le défaut suffit, rien à faire. Mais un dev dont le groupe primaire n'est pas standard (ex. GID hôte 1001) doit surcharger via l'environnement, sinon il hérite du défaut 1000 et les fichiers du bind-mount finissent possédés par le mauvais groupe. Vérifiez votre identité avec id :

id -u   # → UID, ex. 1000
id -g   # → GID, ex. 1001

Avec Castor, c'est automatique. Avec un IDE JetBrains/Gateway, exportez USER_ID/GROUP_ID — procédure dans le tutoriel PHPStorm.

Variables internes au Dev Container

Ces variables sont lues à l'intérieur du Dev Container par les scripts shell de .devcontainer/*.sh. Elles ne sont pas définies dans .env — leur défaut est codé dans init-firewall.lib.sh (sourcée par tous les scripts qui en dépendent).

Variable Défaut Rôle
KIREXO_WORKSPACE /app Racine du projet montée dans le devcontainer. Lue par init-firewall.sh, post-start.sh, frankenphp-supervisor.sh et php-healthcheck.sh pour construire les chemins absolus (${KIREXO_WORKSPACE}/.devcontainer/..., ${KIREXO_WORKSPACE}/var/log/devcontainer/frankenphp.log). Permet de tester les scripts depuis l'hôte sans monter le projet en /app, ou de rebrancher la cible vers un autre chemin sans toucher chaque script. Le sudoers (/etc/sudoers.d/init-firewall) fige malgré tout /app/.devcontainer/... en absolu — un changement de KIREXO_WORKSPACE impose un rebuild de l'image pour aligner le sudoers.

Variables CI/CD GitLab

Ces variables sont définies dans Settings → CI/CD → Variables du projet GitLab et lues par les pipelines décrites dans la Référence Pipeline GitLab CI. Elles ne vivent jamais dans .env ni dans .env.local.

Variable Source Rôle
GITLAB_TOKEN À créer manuellement — Personal Access Token (PAT) Token unique du projet, consommé par cinq usages qui ont tous besoin soit de pousser une branche/un tag, soit d'appeler une route de l'API REST que CI_JOB_TOKEN ne couvre pas sur GitLab Free : (1) tag:create (cf. .gitlab/ci/tag.yml) pour appeler POST /projects/:id/repository/tags ; (2) security:composer-audit (cf. .gitlab/ci/security.yml) pour pousser une branche security/composer-* et ouvrir la MR de patch (ou ouvrir une issue de fallback via castor ci:open-issue) ; (3) tools:version-check (cf. .gitlab/ci/dependencies.yml) pour ouvrir une issue via castor ci:open-issue quand un outil épinglé dans le Dockerfile est en retard ; (4) dependencies:renovate (cf. .gitlab/ci/dependencies.yml), où Renovate le lit via le mapping RENOVATE_TOKEN: $GITLAB_TOKEN pour créer branches et merge requests de montée de version ; (5) security:container-issue et security:container-issue:builders (cf. .gitlab/ci/security.yml) pour appeler POST /projects/:id/issues quand au moins une CVE Critical/High est remontée — CI_JOB_TOKEN n'est pas supporté pour cette route sur GitLab Free (la whitelist du job token couvre Registry/Packages/Releases mais pas l'API issues : retour HTTP 401 testé empiriquement, et le réglage « Allow projects to access this project via CI/CD job tokens » n'y change rien — il n'étend que l'accès inter-projets, pas la whitelist des endpoints). Scopes nécessaires : api (création d'issue/MR par les jobs 2, 3, 4 et 5 via le PAT) + write_repository (création de tag par le job 1 et de branches par les jobs 2 et 4). À configurer en masked uniquement (non protected) dans Settings → CI/CD → Variables — la variable doit être disponible aussi sur les feature branches et les MRs, pas seulement sur les refs protégées (les jobs security:container-issue (+ :builders) tournent sur la pipeline de MR / branche courante pour pouvoir ouvrir l'issue d'alerte CVE, et Protect variable la masquerait à ce moment-là). Non utilisé par release:create ni par les jobs Docker — ceux-ci passent par CI_JOB_TOKEN / CI_REGISTRY_PASSWORD (cf. ci-dessous).
CI_REGISTRY_USER Injecté par GitLab Identifiant d'authentification au registry du projet. Utilisé par les jobs docker:build-* pour docker login.
CI_REGISTRY_PASSWORD Injecté par GitLab Mot de passe d'authentification au registry du projet, valide le temps du job.
CI_REGISTRY_IMAGE Injecté par GitLab Préfixe complet du registry du projet (ex. registry.gitlab.com/shaurifr/kirexo). Sert à construire les noms d'images kirexo-base, kirexo-dev, kirexo-prod.
CI_JOB_TOKEN Injecté par GitLab Token automatique du job courant, scopé sur le job (révoqué à sa fin). Utilisé par release:create pour authentifier release-cli. Pas utilisé par security:container-issue (+ :builders) : la whitelist de CI_JOB_TOKEN sur GitLab Free ne couvre pas POST /projects/:id/issues (testé HTTP 401 empiriquement), ces deux jobs passent par GITLAB_TOKEN (cf. ligne au-dessus).
CREATE_TAG Variable de déclenchement (à passer au lancement de pipeline) true pour lancer la pipeline isolée de création de tag (job tag:create, cf. .gitlab/ci/tag.yml). Absente ou différente de true : la pipeline standard quality + test tourne. Voir Créer un tag de release.
FORCE_TAG Variable de déclenchement optionnelle (à passer au lancement de pipeline) true pour outrepasser le garde-fou de tag:create, qui refuse normalement de créer le tag si aucune pipeline success n'existe pour le dernier commit de main. À passer en plus de CREATE_TAG=true. Sans effet hors de la pipeline CREATE_TAG=true. Réservée aux cas exceptionnels. Voir Créer un tag de release.

Pourquoi un PAT et pas un project access token

Kirexo est hébergé sur GitLab.com en tier gratuit, où les project access tokens (et les group access tokens) ne sont disponibles qu'à partir des offres Premium/Ultimate. En gratuit, le seul type de token utilisable en variable CI/CD est un Personal Access Token (PAT) rattaché à un compte. GITLAB_TOKEN est donc un PAT du mainteneur, avec les scopes api + write_repository. Il sert aussi à Renovate, qui le lit via le mapping RENOVATE_TOKEN: $GITLAB_TOKEN défini dans le job dependencies:renovateaucune variable RENOVATE_TOKEN distincte n'est déclarée côté GitLab.

GITLAB_TOKEN : masqué, non protégé

Cocher Mask variable au moment de la création, sans cocher Protect variable. Mask empêche le token d'apparaître en clair si un job l'echoait par mégarde. Protect, en revanche, doit rester décoché : security:container-issue (+ :builders) tourne sur la pipeline de la MR / feature branch courante pour ouvrir l'issue d'alerte CVE, et Protect variable rendrait la variable indisponible sur ces refs non protégées — l'appel POST /projects/:id/issues partirait alors sans PRIVATE-TOKEN et retomberait en HTTP 401. Le PAT doit appartenir à un compte ayant comme rôle minimum Maintainer sur le projet, pour pouvoir créer un tag (l'API repository/tags refuse les rôles inférieurs) ainsi que les branches et MRs de Renovate.

Variables lues côté hôte (avant création du devcontainer)

Certaines variables ne sont pas lues par compose.override.yaml ni par .env.local — elles doivent être exportées dans le shell qui lance l'IDE, avant que PHPStorm/VS Code ne crée le Dev Container. La définition exhaustive vit dans la Référence du Dev Container. Les déclarer dans .env.local n'a pas d'effet (le fichier est lu trop tard, après postCreateCommand) — le modèle .env.local.dist le documente explicitement dans la section dédiée.

Variable Voir
KIREXO_CLAUDE_BYPASS Référence du Dev Container — Variables lues côté hôte