From bfacd781667ca6da59789caf85fbe0a06f106e17 Mon Sep 17 00:00:00 2001 From: bfoujols Date: Wed, 21 Oct 2026 10:35:16 +0200 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20image=20de=20production=20pour?= =?UTF-8?q?=20le=20d=C3=A9ploiement=20avec=20FrankenDeploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dockerfile multi-stage repris d'edu-framework : frankenphp_base, frankenphp_prod (port 8080, utilisateur app, docker-entrypoint) et frankenphp_dev (stage par défaut, profiler Excimer) - docker-entrypoint.sh : attend la base de données puis démarre FrankenPHP - .dockerignore : exclut .env, var/, vendor/, docs et secrets de l'image - compose.yaml : build sur le stage frankenphp_dev - .gitignore : composer.lock n'est plus ignoré, l'image de production installe les versions qu'il contient et FrankenDeploy n'envoie que les fichiers suivis par git Voir studoo-app/edu-framework#169 Co-Authored-By: Claude Opus 5.5 --- .dockerignore | 31 ++++++++++++++ .gitignore | 1 - Dockerfile | 100 +++++++++++++++++++++++++++++++++++++++---- compose.yaml | 5 ++- docker-entrypoint.sh | 70 ++++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 10 deletions(-) create mode 100644 .dockerignore create mode 100755 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7e5ef5c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# Dépendances installées autrement (réinstallées dans l'image ou montées par compose) +vendor/ +venv/ +node_modules/ + +# Git, IDE, CI, caches +.git/ +.github/ +.idea/ +.cache/ +.codacy/ +.DS_Store +.php-cs-fixer.cache +.phpunit.result.cache + +# Données d'exécution (montées en volumes par compose.yaml) +var/ + +# Environnement local : fourni par le volume compose ou par --env-file, +# jamais figé dans l'image +.env + +# Docs et site statique (inutiles à l'exécution) +site/ +docs/ +*.md + +# Secrets de production : montés par FrankenDeploy depuis le serveur +.env.local +.env.*.local +*.sql.gz diff --git a/.gitignore b/.gitignore index c6692dd..238c9fb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ vendor/ var/ composer.phar -composer.lock # Ignore .env file containing secure information .env .env.local diff --git a/Dockerfile b/Dockerfile index bef466d..1e4948d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,9 +2,22 @@ # https://frankenphp.dev/docs/docker/ # # Edu-Framework exige PHP >= 8.4 (voir composer.json et public/index.php) -FROM dunglas/frankenphp:php8.4 +# +# Image multi-stage : +# frankenphp_base -> socle commun (extensions PHP, Composer) +# frankenphp_prod -> image de production (déploiement avec FrankenDeploy, +# voir frankendeploy.yaml et la doc "Déployer son application") +# frankenphp_dev -> image de développement (compose.yaml), stage par défaut +# +# docker build --target frankenphp_prod -t mon-app . => image de production +# docker compose build => image de développement -# Outils CLI : curl (tests d'API, comme pour le poste de travail local) +################### +# Socle commun +################### +FROM dunglas/frankenphp:php8.4 AS frankenphp_base + +# Outils CLI : curl (tests d'API et healthcheck de l'image de production) # et unzip (décompression propre des archives Composer) RUN apt-get update \ && apt-get install -y --no-install-recommends curl unzip \ @@ -32,6 +45,83 @@ RUN install-php-extensions \ intl \ opcache +# Composer (depuis l'image officielle) +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +WORKDIR /app + +ENV COMPOSER_ALLOW_SUPERUSER=1 + +################### +# Production +################### +# Contrat FrankenDeploy : stage nommé frankenphp_prod, HTTP sur le port 8080 +# (le HTTPS est assuré par le Caddy frontal du serveur), utilisateur non-root +# "app" (uid 1000) et script docker-entrypoint +FROM frankenphp_base AS frankenphp_prod + +ENV APP_ENV=prod +# HTTP simple sur le port 8080 (voir Caddyfile) +ENV SERVER_NAME=:8080 + +# Configuration PHP de production +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" +# - OPcache : le code ne change pas sans redéploiement (pas de revalidation) +# - Sessions : stockées dans var/sessions, dossier partagé entre les releases +# (les utilisateurs restent connectés après un déploiement) +COPY < image sans profiler @@ -39,14 +129,8 @@ RUN install-php-extensions \ ARG EDU_PROFILER=1 RUN if [ "$EDU_PROFILER" = "1" ]; then install-php-extensions excimer; fi -# Composer (depuis l'image officielle) -COPY --from=composer:2 /usr/bin/composer /usr/bin/composer - -WORKDIR /app - # Installation des dépendances PHP (couche mise en cache tant que # composer.json / composer.lock ne changent pas) -ENV COMPOSER_ALLOW_SUPERUSER=1 COPY composer.json composer.lock ./ RUN composer install --no-dev --optimize-autoloader --no-interaction --no-progress diff --git a/compose.yaml b/compose.yaml index b714b4f..d10ef50 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,7 +10,10 @@ services: frankenphp: container_name: ${APP_NAME}-http-server - build: . + build: + context: . + # Stage de développement du Dockerfile (frankenphp_prod sert au déploiement) + target: frankenphp_dev # tty: true => logs lisibles en dev ; retirer pour des logs JSON (prod) tty: true environment: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..6fc01d5 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,70 @@ +#!/bin/sh +# Edu-Framework - point d'entrée de l'image de production (stage frankenphp_prod) +# +# Attend que la base de données accepte les connexions, puis démarre FrankenPHP. +# La base est décrite par DATABASE_URL (base gérée par FrankenDeploy) ou par les +# variables DB_* (DB_HOST_STATUS=true), passées par Docker ou le fichier .env.local. +set -e + +log() { + echo "[Edu-Framework] $1" +} + +# Hôte et port de la base de données, calculés par le framework lui-même +# (même lecture des variables que l'application : environnement, .env.local, .env) +db_target() { + # Code PHP entre guillemets simples : les $ ne doivent pas être interprétés par le shell + # shellcheck disable=SC2016 + php -r ' + require "/app/vendor/autoload.php"; + use Studoo\EduFramework\Core\ConfigCore; + new ConfigCore([]); + try { + ConfigCore::loadEnv("/app/"); + } catch (Throwable $exception) { + // Configuration invalide (Exemple: DATABASE_URL mal formée) : arrêt explicite + fwrite(STDERR, $exception->getMessage() . PHP_EOL); + exit(1); + } + if (ConfigCore::getEnv("DB_HOST_STATUS") !== "true" + || in_array(ConfigCore::getEnv("DB_TYPE"), ["mysql", "mariadb", "pgsql"], true) === false + || ConfigCore::getEnv("DB_HOST") === "") { + exit; + } + $port = ConfigCore::getEnv("DB_SOCKET"); + if ($port === "") { + $port = ConfigCore::getEnv("DB_TYPE") === "pgsql" ? "5432" : "3306"; + } + echo ConfigCore::getEnv("DB_HOST") . " " . $port; + ' +} + +wait_for_database() { + if ! target=$(db_target); then + log "Configuration invalide : vérifiez les variables d'environnement (frankendeploy env list prod)" + exit 1 + fi + if [ -z "$target" ]; then + return 0 + fi + + host=${target% *} + port=${target#* } + log "Attente de la base de données $host:$port..." + attempts=0 + # shellcheck disable=SC2016 + until php -r 'exit(@fsockopen($argv[1], (int) $argv[2], $e, $s, 2) ? 0 : 1);' "$host" "$port"; do + attempts=$((attempts + 1)) + if [ "$attempts" -ge 60 ]; then + log "Base de données injoignable" + exit 1 + fi + sleep 1 + done + log "Base de données prête" +} + +wait_for_database + +log "Démarrage de FrankenPHP" +exec docker-php-entrypoint "$@"