Déclarer PHP 8.4 et alléger l'interpolation du compose

Le premier déploiement Coolify a échoué sur « Lazy loading proxies require
PHP 8.4 or higher ». La cause n'était pas dans le dépôt — Coolify avait
construit avec Nixpacks au lieu du compose — mais l'échec a mis au jour une
inexactitude réelle.

composer.json annonçait « php >=8.3 » alors que l'application exige 8.4 :
config.platform le pinçait déjà à 8.4.0, et les proxies de chargement paresseux
de Symfony ne fonctionnent pas en dessous. Toute chaîne de packaging qui lit la
contrainte pouvait donc choisir 8.3 en toute bonne foi, et échouer bien plus
loin sur un message sans rapport apparent. Contrainte remise à >=8.4.

compose.prod.yaml n'utilise plus `${VAR:?message}` : la forme arrête
correctement `docker compose`, mais le parseur de Coolify ne la comprend pas
partout. Le refus reste porté par docker/entrypoint.prod.sh, où il est testé et
où le message dit quoi faire plutôt que de citer un nom de variable.

Ajout au passage du garde-fou manquant : MARIADB_PASSWORD oubliée produisait
« mysql://tqslator:@database:… », un DSN valide au mot de passe vide, dont
l'échec remonte en erreur de droits d'accès sans jamais nommer la variable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Stephan Morand 2026-08-21 11:12:25 +02:00
parent ad104c82d7
commit 4766c57dda
5 changed files with 48 additions and 18 deletions

View file

@ -455,16 +455,30 @@ elle aucun projet ne peut être créé faute de langue source à choisir.
### Déployer via Coolify
1. **Nouvelle ressource → Docker Compose**, dépôt
> **Le build pack doit être « Docker Compose ».** C'est le réglage qui décide de
> tout le reste, et Coolify ne le devine pas correctement : à la création d'une
> ressource il détecte « application PHP » et choisit **Nixpacks**, qui ignore le
> Dockerfile comme le compose, installe PHP 8.3 et lance
> `composer install --ignore-platform-reqs` — dépendances de développement
> comprises. Le build échoue alors sur
> `Lazy loading proxies require PHP 8.4 or higher`, une erreur qui ne dit rien du
> vrai problème : ce n'est pas le bon constructeur qui tourne.
1. **Nouvelle ressource → Docker Compose** (surtout pas *Nixpacks*), dépôt
`ssh://git@git.tranquilys.com:22222/Stephan/TQ-Slator.git`, branche `main`.
Sur une ressource existante : *Configuration → General → Build Pack*.
2. Fichier compose : `compose.prod.yaml`. Ne pas laisser `compose.yaml`, que
Coolify propose par défaut.
Coolify propose par défaut — Docker y fusionnerait `compose.override.yaml`.
3. Renseigner les variables du tableau ci-dessus dans l'onglet *Environment*.
4. Attribuer le domaine au service **`php`**. `SERVICE_FQDN_PHP_80` est déjà
déclaré dans le fichier : Coolify y place le domaine et configure son proxy.
5. Déployer, puis lancer la commande d'amorçage ci-dessus depuis le terminal de
la ressource.
Le domaine attribué par défaut est en `http://…sslip.io`. Il fonctionne, mais
les liens d'invitation partiront en clair : posez un vrai domaine en HTTPS avant
d'inviter qui que ce soit.
Le TLS est terminé par le proxy de Coolify ; Caddy a donc `auto_https off`, et
`SYMFONY_TRUSTED_PROXIES` est renseigné pour que Symfony lise
`X-Forwarded-Proto` — sans quoi les liens d'invitation partiraient en `http://`

View file

@ -45,17 +45,20 @@ services:
# concerne que la Delivery API appelée depuis un autre domaine.
CORS_ALLOW_ORIGIN: '${CORS_ALLOW_ORIGIN:-^$$}'
# `?` : absente, la variable arrête le déploiement au lieu de laisser
# démarrer un service à moitié configuré.
APP_SECRET: '${APP_SECRET:?openssl rand -hex 32}'
DATABASE_URL: 'mysql://tqslator:${MARIADB_PASSWORD:?mot de passe MariaDB}@database:3306/tqslator?serverVersion=11.4.0-MariaDB&charset=utf8mb4'
# Interpolation simple, sans la forme `${VAR:?message}` : celle-ci arrête
# bien `docker compose` sur une variable manquante, mais le parseur de
# Coolify ne la comprend pas partout. Le refus est donc porté par
# docker/entrypoint.prod.sh, où il est testé — et où le message explique
# quoi faire plutôt que de lister un nom de variable.
APP_SECRET: '${APP_SECRET}'
DATABASE_URL: 'mysql://tqslator:${MARIADB_PASSWORD}@database:3306/tqslator?serverVersion=11.4.0-MariaDB&charset=utf8mb4'
REDIS_URL: 'redis://cache:6379'
# Sert à composer les liens d'invitation envoyés par e-mail. Une valeur
# fausse ne casse rien de visible ici : elle casse la réception, ailleurs,
# plus tard. L'entrypoint refuse donc localhost.
BACK_OFFICE_URL: '${BACK_OFFICE_URL:?URL publique du back-office}'
MAILER_DSN: '${MAILER_DSN:?DSN SMTP réel, Mailpit ne part pas en production}'
BACK_OFFICE_URL: '${BACK_OFFICE_URL}'
MAILER_DSN: '${MAILER_DSN}'
MAILER_FROM: '${MAILER_FROM:-no-reply@tranquilys.com}'
DEFAULT_ORGANIZATION_SLUG: '${DEFAULT_ORGANIZATION_SLUG:-tranquilys}'
@ -83,11 +86,11 @@ services:
environment:
APP_ENV: prod
APP_DEBUG: '0'
APP_SECRET: '${APP_SECRET:?openssl rand -hex 32}'
DATABASE_URL: 'mysql://tqslator:${MARIADB_PASSWORD:?mot de passe MariaDB}@database:3306/tqslator?serverVersion=11.4.0-MariaDB&charset=utf8mb4'
APP_SECRET: '${APP_SECRET}'
DATABASE_URL: 'mysql://tqslator:${MARIADB_PASSWORD}@database:3306/tqslator?serverVersion=11.4.0-MariaDB&charset=utf8mb4'
REDIS_URL: 'redis://cache:6379'
BACK_OFFICE_URL: '${BACK_OFFICE_URL:?URL publique du back-office}'
MAILER_DSN: '${MAILER_DSN:?DSN SMTP réel}'
BACK_OFFICE_URL: '${BACK_OFFICE_URL}'
MAILER_DSN: '${MAILER_DSN}'
MAILER_FROM: '${MAILER_FROM:-no-reply@tranquilys.com}'
DEFAULT_ORGANIZATION_SLUG: '${DEFAULT_ORGANIZATION_SLUG:-tranquilys}'
# Ne migre pas : il ATTEND que le schéma soit à jour.
@ -119,8 +122,8 @@ services:
environment:
MARIADB_DATABASE: tqslator
MARIADB_USER: tqslator
MARIADB_PASSWORD: '${MARIADB_PASSWORD:?mot de passe MariaDB}'
MARIADB_ROOT_PASSWORD: '${MARIADB_ROOT_PASSWORD:?mot de passe root MariaDB}'
MARIADB_PASSWORD: '${MARIADB_PASSWORD}'
MARIADB_ROOT_PASSWORD: '${MARIADB_ROOT_PASSWORD}'
command:
# Doit correspondre à default_table_options de doctrine.yaml, sinon les
# tables créées hors migration divergent en silence. Voir §3.6 de

View file

@ -6,7 +6,7 @@
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.3",
"php": ">=8.4",
"ext-ctype": "*",
"ext-iconv": "*",
"ext-intl": "*",

6
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "cdd141b5f1000a8c6bde9febd6fba449",
"content-hash": "5b8da7895e6fa84a780b9b1972339e4e",
"packages": [
{
"name": "api-platform/doctrine-common",
@ -11925,7 +11925,7 @@
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": ">=8.3",
"php": ">=8.4",
"ext-ctype": "*",
"ext-iconv": "*",
"ext-intl": "*",
@ -11937,5 +11937,5 @@
"platform-overrides": {
"php": "8.4.0"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.9.0"
}

View file

@ -28,6 +28,19 @@ if [ -z "${DATABASE_URL:-}" ]; then
exit 1
fi
# Le DSN est composé dans compose.prod.yaml à partir de MARIADB_PASSWORD.
# Oubliée, la variable produit « mysql://tqslator:@database:… » — un DSN
# syntaxiquement valide, avec un mot de passe vide. MariaDB refuse alors la
# connexion, et le message qui remonte parle de droits d'accès, jamais de la
# variable manquante.
case "$DATABASE_URL" in
*://*:@*)
echo '[entrypoint] Le mot de passe de la base est vide dans DATABASE_URL.' >&2
echo '[entrypoint] Renseignez MARIADB_PASSWORD.' >&2
exit 1
;;
esac
# BACK_OFFICE_URL sert à composer les liens d'invitation. Laissé sur sa valeur
# de développement, il enverrait des e-mails pointant vers localhost — une
# panne qui ne se voit que du côté du destinataire, plusieurs jours plus tard.