phpfpm
Deux types d'image
Deux types d'images sont proposés et basées sur l'image "php" Officielle de Docker. Il s'agit d'une image embarquant PHP FPM.
Deux bases sont maintenues : Debian, Alpine.
Seules les versions de PHP maintenues sont construites :
-
La base Alpine est en version 3.22, PHP versions 8.1 à 8.5, exemple de tag: 8.5-alpine
-
Les bases Debian sont :
- bullseye (11) : 8.1 à 8.4, exemple de tag: 8.4-bullseye [DEPRECATED]
- bookworm (12) : 8.1 à 8.5, exemple de tag: 8.5-bookworm
- trixie (13) : 8.4 à 8.5, exemple de tag: 8.5-trixie
De vieux tags (7.3, 7.4, CentOS, AlmaLinux, docker-debian, docker-alpine...) existent peut-être encore sur le registre et seront purgées automatiquement (entretien automatique du registre). Ne pas les utiliser semble raisonnable. L'image basée sur CentOS n'est plus construite depuis le 1er juillet 2024 (fin de vie de CentOS 7).
Mise à jour "système"
Pour chaque image, un upgrade des packages de la distribution est réalisé au moment du build (quotidien & automatisé). Il n'est donc pas nécessaire de le faire dans vos builds...
Au contraire, cela pourrait nuire: les permissions de certains composant de l'image peuvent être ajustées pour permettre l'auto-configuration en root-less. Mettre à jour les packages depuis votre build risque de réinitialiser ces permissions et de compromettre le démarrage du conteneur.
Configuration
Les deux images intègrent les éléments permettant :
- la supervision (healthcheck et metriques)
- le choix de l'identité des processus
- le tuning de la charge (variables
PM_...= process Mamagement) - le tuning fin du fichier "php.ini" par des variables d'environnement
PHP__xxxxx - la remontée de logs en vue de leur historisation
Utilisation
FROM registry.ademe.fr/docker/phpfpm:8.5-trixie
COPY --chown=www-data:www-data src/ /var/www/html
COPY --chmod=755 ep-app.sh /ep.d/
HEALTHCHECK CMD /bin/true
Script de démarrage
Ici, les actions spécifiques à executer au démarrage sont réalisées par votre script ep-app.sh. Celui-ci, s'il est exécutable, sera exécuté par un sous-shell de l'entry-point du conteneur. S'il ne l'est pas (exécutable), alors il sera sourcé (utile s'il définit des variables d'environnement dont "l'appelant" doit bénéficier).
Supervision & Healthcheck
En vue d'une possibilité de supervision & de monitoring, il est souhaité que chaque composant offre son propre healthcheck qui ne parle que de lui.
Cela est indépendant de la page de test de vie générale des applications ou des API qui doit documenter un certain nombre de tests permettant d'annoncer l'état de santé général de l'application et de ses composants applicatifs et fonctionnels.
Dans cette image, cela se traduit par la définition, dans l'image de départ, ce ce qui suit :
HEALTHCHECK --start-period=5s --interval=5s --timeout=5s --retries=5 CMD /usr/local/bin/healthcheck.sh
# Status
ENV PHPFPM_STATUS_URI=/fpmstatus
ENV PHPFPM_ENABLE_STATUS=true
Ces variables sont nécessaires pour le script healthcheck.sh et sont consommées par l'entry-point réalisant la configuration du pool PHP.
Le script healthcheck.sh procède à un appel FastCGI pour interroger /fpmstatus et y collecter des métriques.
Si votre application basée sur cette image interdit pour une raison ou une autre que /fpmstatus soit contacté, vous ne pouvez pas déployer, car le HEALTHCHECK hérité ne retournera jamais de succès, et votre conteneur/service sera détruit/relancé indéfiniment par l'orchestrateur.
Solution :
-
"dans l'urgence" si nécessaire : ajouter dans votre Dockerfile une surcharge de la déclaration héritée :
HEALTHCHECK CMD /bin/trueou, si c'est plus simple, passer la variable
PHPFPM_ENABLE_STATUS=falseà votre conteneur.Votre service démarrera donc... mais débrancher une sonde de température ne corrige pas un problème de surchauffe.
-
à terme: permettre au script
healthcheck.shde fonctionner en autorisant la requête FCGI vers/fpmstatus. Le type de réponse attendu est :OK: active=1 idle=4 total=5 listen_queue=0 busy_ratio=0.200 slow=0Cette réponse varie bien entendu en fonction de vos réglages de charge (voir plus loin PM_xxxx) et du contexte mais ressemble à cela :
- OK: "tout va bien". Code retour = 0.
- WARN: "un souci potentiel, voici lequel". Code retour = 0 par défaut
- CRIT: "un vrai problème bloquant, voici lequel". Code retour = 2 par défaut
Le code retour associé, si différent de 0, engendre au bout d'un certain temps un redémarrage du service par l'orchestrateur.
Codes retour du healthcheck
On peut les définir par les variables suivantes :
- EXIT_CODE_CRITICAL=${EXIT_CODE_CRITICAL:-1}
- EXIT_CODE_WARNING=${EXIT_CODE_WARNING:-0}
Composants présents dans l'image
Composer
Versions de Composer : latest v1 & latest v2.
Il n'est pas installé par défaut. Dans vos Dockerfile il faut exécuter le script /usr/local/bin/install-composer.sh (avec éventuellement l'argument 1 pour la V1, sinon, c'est V2 qui sera installée par défaut)
NewRrelic
La dernière version du package "newrelic-php5" disponible sur le (repository)[http://download.newrelic.com/pub/newrelic/el5/x86_64/] peut être installée (il faut le faire dans votre Dockerfile)
Extension disponible sur les variantes suivantes :
- alpine : pas prévu.
- debian : pas installé par défaut, il faut exécuter
/usr/local/bin/install-newrelic.shdans vos Dockerfile pour l'intégrer à vos images. Il faut aussi penser à l'activer au démarrage du conteneur : variable NEWRELIC_ENABLE
Mode de configuration de PHP
Deux approches existent :
- présenter des fichiers de configuration au bon endroit
- utiliser des variables d'environnement.
Il est fortement conseillé d'utiliser des variables d'environnement (https://12factor.net/fr/config).
Méthode 1 : Présenter un fichier de configuration au conteneur
Des éléments de configuration peuvent être déposés sous forme de fichiers ".ini" dans le répertoire /usr/local/etc/php/conf.d, comme pour l'image amont, que ce soit pour la base Debian ou Alpine.
Méthode 2 : Utiliser des variables d'environnement
Toute variable d'environnement préfixée par PHP__ est intégrée en directive de configuration de PHP dans le fichier "99-tuning.ini" au démarrage du conteneur.
Ce fichier de configuration "99-tuning.ini" est automatiquement ajouté dans /usr/local/etc/php/conf.d et donc pris en compte dans tous les cas.
Exemple :
docker container run --rm -it --env PHP__memory_limit=128M ${REGISTRY...}.../phpfpm:8.5-alpine php -r 'echo ini_get("memory_limit"), PHP_EOL;'
Les variables dont le nom contient un "." (exemple session.save_path) doivent être codées en remplaçant le "." par "underscore" "dot" "underscore" :
Exemple :
PHP__session_dot_save_path=valeur
Exemple de déclarations :
$ cat env.txt
PHP__post_max_size=50M
PHP__memory_limit=256M
PHP__upload_max_filesize=29M
PHP__session_dot_save_path=/var/lib/php/fpm/session
Tuning du pool PHP-FPM
Le pool PHP-FPM hérite des variables définies par 99-tuning.ini.
En complément, il a son propre fichier de paramétrage, notamment utilisé que pour gérer l'identité du pool, la localisation des logs et la parie "PM".
Tuning du pool PHP-FPM
- PM_MODE : par défaut "ondemand" (dynamic / static).
- PM_MAX_CHILDREN : par défaut 100.
- PM_MAX_REQUESTS : par défaut 500.
- PM_PROCESS_IDLE_TIMEOUT : par défaut 30s
Si PM_MODE = dynamic :
- PM_START_SERVERS : par défaut 2
- PM_MIN_SPARE_SERVERS : par défaut 2
- PM_MAX_SPARE_SERVERS : par défaut 5
Activation du Status
- PHPFPM_ENABLE_STATUS : désactivé par défaut (true pour actuver)
- PHPFPM_STATUS_URI : "/fpmstatus" par défaut si activé.
Si vous choisissez de désactiver le Status, le healthcheck affichera un warning () mais renverra un code retour de 0. Celui-ci sera donc artificiellement présumé healthy.
Timeouts
Les timeouts suivants sont réglables par les variables ci-dessous :
- PHPFPM_SLOWLOG_TIMEOUT (3 secondes par défaut)
- PHPFPM_REQUEST_TIMEOUT (300 secondes par défaut)
Logs
Les logs sont émis sur stderr des workers. Si possitionnée à yes, la variable suivante permet (c'est contraire à la spec de FastCGI) de les ramener dans le log du processus d'avant-plan :
- PHPFPM_CATCH_WORKERS_OUTPUT (no par défaut)
Cela permet par exemple d'utiliser dans le code une fonction de ce type pour loguer :
function stderr_log($message, $prefix = "INFO") { $stderr = fopen('php://stderr', 'w'); fwrite($stderr, $prefix . $message . PHP_EOL); fclose($stderr); }
Variables pour NewRelic
Les variables suivantes sont disponibles :
-
NEWRELIC_ENABLE (false par défaut) : activer l'extension PHP
-
NEWRELIC_DO_INSTALL (false par défaut) : Exécuter
newrelic-install installou pas (voir les lignes 125 et suivantes du fichier newrelic.ini).
Et pour le fichier de configuration : (Doc)[https://docs.newrelic.com/docs/agents/php-agent/advanced-installation/docker-other-container-environments-install-php-agent/#agent-container]
- NEWRELIC_LICENSE_KEY (vide par défaut) : clé de licence, appliquée dans "newrelic.ini" (PHP).
Variables disponibles uniquement sur la base CentOS
Réglages système
- LDAP_TLS_REQCERT : valeur affectée à TLS_REQCERT dans /etc/opendldap/ldap.conf (par défaut : never)
- LANG : valeur par défaut = fr_FR.UTF-8
- TIMZEONE : valeur par défaut = Europe/Paris
Autre solution : bind mount de /etc/localtime :
--volume /etc/localtime:/etc/localtime:ro.
Pour l'émission de mail
L'idéal est d'utiliser la classe PHPMailer et de la configurer pour utiliser un service SMTP authentifié. Sans cela, il reste possible d'utiliser la commande "sendmail". Le paquet ssmtp est installé et la fournit.
- SMTP_RELAY_HOST : nom du relayhost = unconfigured
- SMTP_RELAY_PORT : port du relayhost = 25
- SMTP_DOMAIN_NAME : nom de domaine émetteur par défaut = localdomain
- SMTP_REWRITE_FROM : Valeur de la directive FromLineOveride (NO) par défaut.
Logs
Ils ne restent pas dans le conteneur :
- AccessLog vers stdout
- ErrorLog vers stderr
- SlowLog vers stderr
Cela représente un changement par rapport aux images officielles PHP qui envoient tout vers stderr.
Ils peuvent ainsi être consultés par docker container logs ou transmis à un récepteur tiers.
Le timeout de traçage des requêtes dites "slow" (request_slowlog_timeout) est réglable par la variable d'environnement PHPFPM_SLOWLOG_TIMEOUT (3 secondes par défaut).
PHPFPM derrière un reverse-proxy
Les deux fichiers suivants donnent un exemple simplissime d'utilisation de l'image phpfpm derrière l'image httpd.
Ici, l'application est d'abord packagée dans une image intermédiaire, puis intégrée, grâce au même Dockerfile, dans l'image httpd et dans l'image phpfpm (voir l'argument BASE_IMAGE dans compose.yaml).
compose.yaml
networks:
app:
services:
httpd:
build:
context: .
args:
BASE_IMAGE: registry.ademe.fr/docker/httpd:2.4-alpine
restart: always
hostname: webgw.local
networks:
- app
environment:
- HTTPD_ENABLE_PHPFPM_INET=true
ports:
- "8000:80"
phpfpm:
build:
context: .
args:
BASE_IMAGE: registry.ademe.fr/docker/phpfpm:8.5-trixie
restart: always
networks:
- app
Dockerfile
ARG BASE_IMAGE
# ======================
# Empaquetage de l'appli
# ======================
FROM alpine as appbuilder
# RUN ....
# ADD ...
# COPY ...
RUN mkdir /app
RUN echo "Servi directement par Apache." > /app/index.html
RUN echo "<?php printf ('Servi par Apache->phpfpm (%s)' , gethostname() ); ?> " > /app/index.php
# ============================
# On va construire le livrable
# ============================
# ARG permet d'utiliser le même dockerfile pour httpd et pour phpfpm
# + on récupère l'app déjà buildée (pour gagner le cache, on builde plus rapidement)
FROM ${BASE_IMAGE}
COPY --from=appbuilder --chown=www-data:www-data /app /var/www/html
Environnement de développement - rootless
Identité / Permisisons
Le conteneur peut être lancé avec une identité arbitraire.
Par défaut, si le conteneur est lancé en tant que root, les processus Apache auront l'identité www-data:www-data (33:33).
La directive user: uuu:ggg de docker compose ou la directive --user de docker container run permettent de choisir une identité arbitraire (qui n'est pas nécessairement existante dans le conteneur, ni sur l'hôte) telle qu'on l'entend dans la clause user: "1971:1976" d'un fichier compose.yaml. Tous les processus du conteneur auront alors cette idéndité, qui doit donc poséder les droits de lecture/écriture nécessaires au bon fonctionnement de l'application.
Si la variable FIX_WEBCONTENT_OWNER est positionnée à true, le propriétaire et le groupe des fichiers présents dans /var/www/html sera positionné à www-data:www-data (ou à l'identité choise au lancement du conteneur) pour assurer le droit de lecture/écriture par le serveur HTTP. Cette variable n'est pas définie par défaut.
C'est quand-même nettement plus propre de fixer les propriétaires et permissions au moment de l'import de vos fichiers dans l'image :
COPY --chown=... --chmod=...
Identité du pool
Utilisez le nom d'utilisateur www-data !
- sur l'image officielle PHP/Debian, on est en standard sur un user/group (www-data:33)
- sur l'image officielle PHP/Alpine, on est en standard sur un user/group (www-data:82)
Dans ces images, le pool PHP-FPM est sur toutes les variantes configuré pour fonctionner en tant que user www-data (uid=33) et groupe www-data (gid=33) si le conteneur est lancé avec l'identité de root (par défaut), et les directives User et Group sont retirées de la configuration du pool dans le cas où le conteneur est lancé sous une identité arbitraire. Ainsi, les processus php-fpm ont l'identité arbitraire choisie.
Utilisateur "de développement"
Pour construire des images de DEV basées sur celle-ci, mais devant tourner avec un autre UID que "www-data", on peut procéder via sudo, c'est l'approche initiale utilisée par cette image.
En effet, si la variable USE_SUDO vaut true, alors, le conteneur teste la présence de "sudo", et s'il est présent, exécute tous les scripts d'entrypoint via sudo, pour autoriser les ajustements de configuration même si le conteneur tourne sous une autre identité.
Aujourd'hui l'image permet un lancement sous une identité arbitraire, ce qui règle le problème plus simplement. L'approche sudo pourrait désormais être retirée dans une version future.
Exemple de fichier "Dockerfile.userlocal"
# Pour autoriser des images de DEV basées sur celles-ci à tourner avec un autre UID, via sudo
ARG IMG
FROM ${IMG}
## Add local user
ARG USER_NAME
ARG USER_UID
RUN useradd ${USER_NAME} --uid ${USER_UID}
RUN set -eux \
&& if [ -f /etc/centos-release ] ; then yum -y install sudo && yum clean all ; fi \
&& if [ -f /etc/alpine-release ] ; then apk add --no-cache sudo ; fi \
&& if [ -f /etc/debian_version ] ; then apt-get update && apt-get -y install sudo && apt-get -y clean ; fi \
&& echo "${USER_NAME} ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/localuser
ENV USER_NAME ${USER_NAME}
ENV USE_SUDO true
Exemple de lancement :
$ cat Makefile
IMAGE=phpfpm
BASEIMAGE=registry.actilis.net/docker-images/phpfpm:8.1-docker-alpine
LOCALUSER_NAME=fmicaux
LOCALUSER_UID=1000
localuserbuild: ## service PHPFPM dans image de dev en tant que user local
localuserbuild:
tar c Dockerfile.localuser | docker image build -t $(IMAGE):localuser --build-arg IMG=$(BASEIMAGE) --build-arg USER_NAME=$(LOCALUSER_NAME) --build-arg USER_UID=$(LOCALUSER_UID) -f Dockerfile.localuser -
localuser: ## service PHPFPM dans image de dev en tant que user local
localuser: localuserbuild
docker container run --name ${IMAGE}-test --user $(LOCALUSER_NAME) --rm -dit $(IMAGE):localuser
docker container logs -f ${IMAGE}-test
stop:
@docker container kill ${IMAGE}-test || true
$ make localuser
Extensions présentes au départ dans ces images
[PHP Modules]
Core ctype curl date dom fileinfo filter hash iconv json libxml mbstring mysqlnd openssl pcre PDO pdo_sqlite Phar posix random readline Reflection session SimpleXML sodium SPL sqlite3 standard tokenizer xml xmlreader xmlwriter Zend OPcache zlib
Changements récents
2025-11 :
- healthcheck implicite
- tracking des modifs ici
- refection totale du README