Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
vendor/
var/
composer.phar
composer.lock
# Ignore .env file containing secure information
.env
.env.local
Expand Down
100 changes: 92 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -32,21 +45,92 @@ 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 <<EOF $PHP_INI_DIR/conf.d/app.prod.ini
expose_php = Off
opcache.validate_timestamps = 0
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
opcache.interned_strings_buffer = 16
realpath_cache_size = 4096K
realpath_cache_ttl = 600
session.save_path = /app/var/sessions
EOF

# Utilisateur non-root qui exécute l'application
ARG UID=1000
ARG GID=1000
RUN groupadd --gid ${GID} app \
&& useradd --uid ${UID} --gid ${GID} --create-home --shell /bin/bash app

# Dépendances PHP sans les outils de développement (couche mise en cache tant
# que composer.json / composer.lock ne changent pas)
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-autoloader --no-scripts --no-interaction --no-progress --prefer-dist

# Sources de l'application (le .dockerignore exclut .env, var/, vendor/, docs...)
COPY --chown=${UID}:${GID} . ./
RUN composer dump-autoload --optimize --no-dev

# Configuration du serveur
COPY Caddyfile /etc/frankenphp/Caddyfile

# Dossiers d'exécution accessibles en écriture par l'utilisateur app
# (var/logs, var/sessions, var/sqlite et public/upload sont des dossiers
# partagés montés par FrankenDeploy, voir frankendeploy.yaml)
RUN mkdir -p var/cache var/logs var/sessions var/sqlite public/upload \
&& chown -R app:app var public/upload /data /config

# Attend la base de données puis démarre FrankenPHP
COPY --chmod=755 docker-entrypoint.sh /usr/local/bin/docker-entrypoint
ENTRYPOINT ["docker-entrypoint"]
CMD ["--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"]

# Sonde applicative : vérifie que l'application répond, pas seulement Caddy
# (même URL que healthcheck_path dans frankendeploy.yaml)
HEALTHCHECK --start-period=60s CMD curl -f http://localhost:8080/ || exit 1

USER app

EXPOSE 8080

###################
# Développement (stage par défaut, utilisé par compose.yaml)
###################
FROM frankenphp_base AS frankenphp_dev

# Profiler de code de la barre de debug (Excimer, profiler par échantillonnage)
# Installé uniquement pour l'image de développement :
# docker compose build --build-arg EDU_PROFILER=0 => image sans profiler
# Doc : https://www.mediawiki.org/wiki/Excimer
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

Expand Down
5 changes: 4 additions & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
70 changes: 70 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"