Référence du Dockerfile multi-stage¶
Inventaire factuel du Dockerfile racine de Kirexo. Pour comprendre pourquoi l'image est bâtie sur Alpine et pourquoi ce découpage en stages, voir l'explication Pourquoi une image Alpine multi-stage. Pour l'outillage spécifique au devcontainer (pare-feu, cycle de vie, cibles Castor), voir la Référence du Dev Container.
Cette page décrit l'état réel du Dockerfile ; en cas de doute, le fichier fait foi.
Image de base¶
Un unique point de pin, réutilisé par tous les stages :
| Élément | Valeur |
|---|---|
| Image amont | dunglas/frankenphp:1-php8.5-alpine |
| Distribution | Alpine Linux (branche release courante de l'amont, v3.24) |
| libc | musl |
| Serveur applicatif | FrankenPHP (Caddy + worker PHP) |
Le tag est figé par une instruction FROM — pas un ARG. La détection d'un tag plus récent est faite par castor devcontainer:upgrade-tools via ToolsUpgrader::selectFrankenphpCandidate(), dont le parsing reconnaît le suffixe -alpine (cf. Surveillance de l'image base FrankenPHP).
Les trois stages¶
Le Dockerfile définit trois stages construits en images séparées, chaînés par héritage.
| Stage | Hérite de | Rôle | Utilisateur d'exécution |
|---|---|---|---|
frankenphp_base |
frankenphp_upstream |
Extensions PHP, Composer, Castor. Socle commun. | root |
frankenphp_dev |
frankenphp_base |
Xdebug, Chromium, Node, outillage devcontainer et pare-feu. | app (UID/GID hôte, défaut 1000:1000) |
frankenphp_prod |
frankenphp_base |
composer install --no-dev, sources applicatives, durcissement CVE. |
app (1000:1000, shell /bin/sh) |
frankenphp_dev et frankenphp_prod dérivent tous deux de frankenphp_base (et non l'un de l'autre) : le stage dev n'est jamais embarqué en production. Ces trois stages correspondent aux images kirexo-base, kirexo-dev, kirexo-prod publiées dans le registry (cf. Images publiées dans le registry).
Paquets système par stage¶
Tous les paquets sont installés via apk add --no-cache (un index apk qui n'est jamais écrit dans l'image). Chaque stage a son ou ses appels dédiés.
frankenphp_base¶
| Paquet | Rôle |
|---|---|
acl |
Gestion des ACL POSIX (héritée du template amont). |
curl |
Client HTTP — requis par le HEALTHCHECK (curl -f http://localhost:2019/metrics) et le téléchargement du PHAR Castor. Paquet Alpine distinct de busybox. |
file |
Détection de type MIME. |
gettext |
envsubst (substitution de variables dans les templates de conf). |
git |
Requis par Composer pour les dépendances de type source. |
jq |
Parsing JSON — hooks Claude, statusline, plusieurs jobs CI. Présent dès kirexo-base, donc pas de apk add à la volée côté pipeline. |
unzip |
Décompression des archives dist Composer. |
frankenphp_dev¶
Le stage dev fait plusieurs apk add séparés (pour maximiser le cache de layer) :
apk add --no-cache bash— installé avant la directiveSHELL(cf. Note musl) ;- Chromium et son driver (cf. Chromium épinglé sur une branche Alpine figée) ;
- l'outillage devcontainer et pare-feu, en un seul appel :
apk add --no-cache \
sudo iptables ip6tables ipset dnsmasq ca-certificates iproute2 \
musl-utils coreutils procps findutils shadow libcap \
shellcheck bash-completion bats ffmpeg xz
| Paquet | Rôle |
|---|---|
sudo |
post-start.sh lance sudo init-firewall.sh (NOPASSWD ciblé, cf. Convention sudo). |
iptables / ip6tables / ipset / dnsmasq / iproute2 |
Cœur du pare-feu sortant du devcontainer. |
ca-certificates |
Validation TLS des smoke-tests curl du pare-feu. |
musl-utils |
Fournit getent (absent de busybox/musl) — requis par init-firewall.sh et la création d'utilisateur. |
coreutils |
date -Iseconds, mktemp, seq — les applets busybox ne garantissent pas ces options. |
procps |
pgrep -x / pkill -x (busybox ne fournit pas -x). |
findutils |
xargs -L 1 (busybox xargs n'a pas -L). |
shadow |
useradd / groupadd (ce stage) et usermod / groupmod (overlay compose) — busybox n'a qu'adduser / addgroup. |
libcap |
setcap cap_net_bind_service sur le binaire FrankenPHP. |
shellcheck |
Linter des scripts shell (cible castor lint:shell). |
bash-completion |
Framework requis par la complétion Castor / Symfony Console. |
bats |
castor test:bash (bats-core 1.x, paquet Alpine bats). |
ffmpeg |
Capture vidéo des démos E2E. |
xz |
Extraction du tarball Node .tar.xz (busybox tar délègue xz à un binaire externe). |
git, jq et curl viennent déjà de frankenphp_base — ils ne sont pas redemandés.
frankenphp_prod¶
Le stage prod n'ajoute aucun paquet applicatif ; il exécute un seul appel de durcissement et un paquet de build temporaire :
apk upgrade --no-cache tire les correctifs de sécurité Alpine sur tous les paquets hérités de frankenphp_base (curl, libxml2, expat, ncurses, openssl…). C'est le cœur du gain CVE sur l'image livrée. Il remplace l'ancien apt-get purge --autoremove linux-libc-dev du stage Debian : ce paquet n'existe pas sous Alpine (les headers kernel sont dans linux-headers, non installé par défaut), la purge est donc sans objet.
libcap est installé comme dépendance de build virtuelle (.setcap-deps) puis retiré après le setcap : la capability gravée dans les xattr du binaire survit à la suppression de l'outil, sans laisser libcap en surface d'exécution.
Extensions PHP¶
Installées via install-php-extensions (compatible Alpine — il résout les libs musl requises).
| Stage | Extensions | Lib musl résolue automatiquement |
|---|---|---|
frankenphp_base |
apcu fileinfo intl opcache zip pdo_pgsql redis amqp |
icu-libs (intl), libpq / postgresql-libs (pdo_pgsql), rabbitmq-c (amqp), libzip (zip) |
frankenphp_dev |
xdebug |
— |
L'extension amqp (liée à rabbitmq-c sous Alpine) était le candidat le plus à risque de la migration ; elle est bien résolue par install-php-extensions.
Bloc recipes Symfony Flex
Un install-php-extensions pdo_pgsql supplémentaire figure dans le bloc ###> doctrine/doctrine-bundle ### du Dockerfile (recette Flex). Il est redondant avec l'appel groupé ci-dessus et sans effet (extension déjà installée).
Binaires installés hors gestionnaire de paquets¶
Trois binaires sont téléchargés directement (pas via apk), chacun figé par version et vérifié par SHA-256 avant installation.
| Binaire | Stage | ARG version | Source | Vérification |
|---|---|---|---|---|
| Composer | frankenphp_base |
composer:2.10 (tag d'image) |
COPY --from=composer:2.10 |
Image officielle Composer (pas de téléchargement réseau). |
| Castor | frankenphp_base |
CASTOR_VERSION (v1.6.1) |
github.com/jolicode/castor (PHAR) |
CASTOR_SHA256 via sha256sum -c. |
| glab | frankenphp_dev |
GLAB_VERSION (1.108.0) |
gitlab.com/gitlab-org/cli (Go statique) |
GLAB_SHA256 via sha256sum -c. |
| Node / npm / npx | frankenphp_dev |
NODE_VERSION (24.18.0) |
unofficial-builds.nodejs.org — variante musl |
NODE_SHA256 via sha256sum -c. |
| Claude Code | frankenphp_dev |
CLAUDE_CODE_VERSION (2.1.211) |
npm install -g @anthropic-ai/claude-code |
dist.integrity SRI validé nativement par npm. |
Le tarball Node utilise la variante musl — le build glibc de nodejs.org/dist ne tourne pas sous Alpine :
https://unofficial-builds.nodejs.org/download/release/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64-musl.tar.xz
ToolsUpgrader::buildDockerfileUrls() (castor/src/DevContainer/ToolsUpgrader.php) reconstruit cette même URL musl pour le pré-check host:check. Le pin version + SHA-256 est bumpé par castor devcontainer:upgrade-tools.
Chromium épinglé sur une branche Alpine figée¶
Le stage frankenphp_dev installe Chromium et son driver depuis la branche release figée v3.24 d'Alpine, pas depuis le dépôt community « rolling » de la base :
ARG CHROMIUM_ALPINE_BRANCH=v3.24
ARG CHROMIUM_VERSION=150.0.7871.128-r0
RUN apk add --no-cache \
--repository "https://dl-cdn.alpinelinux.org/alpine/${CHROMIUM_ALPINE_BRANCH}/community" \
"chromium=${CHROMIUM_VERSION}" \
"chromium-chromedriver=${CHROMIUM_VERSION}"
| Élément | Valeur |
|---|---|
| Version Chromium | 150.0.7871.128-r0 (Alpine v3.24) |
| Branche du dépôt | v3.24 (release figée, mêmes libs que la base) |
| Binaires | /usr/bin/chromium et /usr/bin/chromedriver |
| Paquets | chromium + chromium-chromedriver (même version source → toujours alignés) |
Un smoke-test fail-fast suit immédiatement : il fait échouer docker build si le navigateur ne démarre pas.
chromium --headless=new --no-sandbox --disable-dev-shm-usage --dump-dom about:blank > /dev/null
chromedriver --version
Les flags sont alignés sur PANTHER_CHROME_ARGUMENTS de .env.test. Le rationale du pin sur une branche figée (plutôt qu'un pin nu qui casse au refresh) est dans l'explication.
Renommages de paquets Debian vers Alpine¶
La migration a changé le nom de plusieurs paquets et outils. Ces correspondances sont utiles pour relire l'historique ou un ancien Dockerfile Debian.
| Rôle | Nom Debian | Nom Alpine |
|---|---|---|
| Driver Chromium (Panther/CI) | chromium-driver |
chromium-chromedriver |
Lib AMQP (extension amqp) |
librabbitmq |
rabbitmq-c |
Lib PostgreSQL (extension pdo_pgsql) |
libpq |
postgresql-libs |
getent (résolution hosts) |
fourni par libc-bin (glibc) |
fourni par musl-utils |
| Création d'utilisateur (dev) | useradd / groupadd (paquet passwd) |
useradd / groupadd (paquet shadow) |
| Création d'utilisateur (prod) | useradd / groupadd |
adduser / addgroup (busybox) |
| Headers kernel | linux-libc-dev |
linux-headers (non installé) |
Note musl : bash, shell et création d'utilisateur¶
Alpine ne préinstalle pas bash — /bin/sh est busybox ash. Le Dockerfile en tient compte :
apk add bashavantSHELL: le stagefrankenphp_devbascule surSHELL ["/bin/bash", "-o", "pipefail", "-c"](nécessaire pourpipefailsur les pipelinescurl … | sha256sum -c). Comme la directiveSHELLéchouerait sur/bin/bash: not found,bashest installé juste avant, avec le shell par défaut (ash). bash est aussi requis parinit-firewall.sh(arrays,mapfile,[[ =~ ]]).- Complétion Castor dans
/etc/profile.d/: Alpine ne garantit pas de/etc/bash.bashrc. La complétion est écrite dans/etc/profile.d/castor-completion.sh, sourcé par tout shell de login. Cf. Autocomplétion Castor. useradd/groupadden dev,adduser/addgroupen prod : le stage dev installeshadow(pour aligner l'UID/GID hôte viauseradd/groupaddetgetent) ; le stage prod, plus minimal, se contente des applets busyboxadduser/addgroupavec un UID/GID fixe1000:1000et un shell/bin/sh(bash n'est pas installé en prod).