Skip to main content

Bonnes pratiques pour créer un Dockerfile prêt pour la production pour les applications PHP

Écrit par
Headshot of James Walker

James Walker

blog feature multi namespace k8s controller

22 août 2023

0 minutes de lecture

Docker est une plateforme de conteneurisation qui regroupe votre code, ses dépendances et son environnement d’exécution dans des unités autonomes qui s’exécutent de manière identique dans différents environnements.

La conteneurisation d’une application PHP avec Docker simplifie son déploiement en regroupant l’environnement d’exécution PHP, un serveur web, votre code source et vos dépendances Composer dans un conteneur. Il est facile de se lancer avec Docker. Toutefois, vous devez éviter quelques écueils avant de pouvoir l’utiliser en toute sécurité en production. Pour prévenir les problèmes, il est essentiel de choisir une image de base adaptée, de sélectionner l’environnement d’exécution PHP correspondant à votre charge de travail et de renforcer la sécurité de votre image. Dans ce tutoriel, vous apprendrez à écrire un Dockerfile fiable pour les applications PHP.

Créer un Dockerfile PHP prêt pour la production

Si vous lisez cet article, vous avez probablement déjà écrit votre application PHP et souhaitez maintenant la conteneuriser avec Docker pour la déployer. Voici dix bonnes pratiques pour optimiser les performances, la sécurité, la fiabilité et la facilité de développement :

1. Utiliser une image de base précise

La communauté Docker publie une image Docker officielle pour toutes les versions de PHP prises en charge. Plusieurs variantes sont disponibles, couvrant différentes combinaisons de systèmes d’exploitation de base, de versions du langage et d’intégrations de serveurs web (comme Apache ou FastCGI Process Manager (FPM)).

Il est important de sélectionner l’image précise qui répond le mieux à vos besoins. Évitez les balises génériques, comme php:latest, car elles risquent d’introduire des changements indésirables susceptibles de casser votre code et des bibliothèques inutiles, nécessaires uniquement à un système d’exploitation complet. Par exemple, l’image php:apache inclut actuellement PHP 8.2, mais passera immédiatement à PHP 9.0 dès la sortie de cette version. En choisissant php:8.2-apache, vous vous assurez que votre image exécutera toujours PHP 8.2, même lorsque cette version ne sera plus la version actuelle :

FROM php:8.2-apache
WORKDIR /var/www/html

COPY index.php .

Le même principe s’applique aux autres images dont vous pourriez dépendre, comme composer pour l’installation des dépendances. Choisissez la balise d’image correspondant à la version majeure de Composer que vous utilisez sur votre machine : composer:2 plutôt que composer:latest.

2. Utiliser des variables d’environnement pour la configuration

Les conteneurs Docker sont conçus pour être sans état. Il est préférable de configurer votre application à l’aide de variables d’environnement définies au démarrage du conteneur. Cette approche est plus pratique que de s’appuyer sur un fichier de configuration, qui vous obligerait à toujours configurer un stockage persistant pour le conteneur à l’aide d’un volume Docker.

Les variables d’environnement se définissent avec l’option -e au démarrage d’un conteneur à l’aide de docker run :

$ docker run -d -e DATABASE_HOST=172.26.0.2 php-app:latest

Votre code PHP peut récupérer la valeur de la variable en appelant la fonction getenv() :

// 172.26.0.2
echo getenv("DATABASE_HOST");

Il s’agit de variables d’environnement d’exécution, définies pour chaque conteneur. Vous pouvez parfois vouloir définir une variable dans votre Dockerfile afin qu’elle soit automatiquement transmise aux conteneurs. Pour cela, utilisez l’instruction ENV :

FROM php:8.2-apache
WORKDIR /var/www/html

ENV DATABASE_DRIVER=sqlite

COPY index.php .

Les variables d’environnement peuvent également être renseignées au moment de la compilation et référencées dans les autres instructions de votre Dockerfile à l’aide du mot-clé ARG :

FROM php:8.2-apache
WORKDIR /var/www/html

ARG DEFAULT_DATABASE_DRIVER

RUN echo '{"db":"$DEFAULT_DATABASE_DRIVER"}' > config.json
COPY index.php .

Pour définir la valeur de l’argument, utilisez l’option --build-arg lorsque vous exécutez docker build :

$ docker build --build-arg DEFAULT_DATABASE_DRIVER=sqlite -t php-app:latest .

3. Choisir entre PHP FPM et Nginx/Apache

FPM est une autre implémentation FastCGI de PHP permettant d’intégrer PHP à des serveurs web, notamment Apache et Nginx. Une autre option que FPM consiste à utiliser le serveur web Apache avec mod_php. Par rapport à cette solution Apache, FPM offre une gestion avancée des processus non bloquants, la possibilité de créer différents pools de processus de travail et une gestion plus souple des erreurs. FPM offre souvent un gain de performances considérable pour les applications volumineuses.

L’image de base Docker de PHP est disponible en variantes FPM et mod_php :

  • php:8.2-fpm : cette image et les balises similaires exposent une connexion FPM.

  • php:8.2-apache : ces images intègrent une installation d’Apache et utilisent mod_php pour traiter les requêtes PHP.

Si vous utilisez Apache et que mod_php vous convient, choisissez l’une des images de base :apache. Vous pouvez ajouter votre code PHP dans le répertoire /var/www/html et l’exécuter immédiatement.

Pour utiliser FPM, vous aurez besoin d’un conteneur distinct exécutant un serveur tel que Nginx. Celui-ci doit être configuré comme proxy inverse qui redirige les requêtes PHP vers le port exposé par le processus FPM dans votre conteneur PHP. Par défaut, le numéro de port est 9000.

Utilisez FPM si vous souhaitez obtenir des performances maximales et que la gestion de deux conteneurs ne vous pose pas de problème : un conteneur FPM et un serveur web en amont. Les images basées sur Apache constituent un excellent point de départ pour être opérationnel rapidement, sans configurer de conteneurs supplémentaires ni affiner la configuration des pools de processus de travail FPM.

4. Désactiver les journaux de débogage et le signalement d’erreurs inutiles en production

Les applications en production ne doivent divulguer publiquement aucune information susceptible d’aider un attaquant. Désactivez les paramètres PHP display_errors et display_startup_errors pour empêcher l’affichage des exceptions non gérées sur votre site web.

Pour modifier ces valeurs, éditez le fichier php.ini dans votre conteneur.

Commencez par créer votre fichier modifié :

display_errors = Off
display_startup_errors = Off

Modifiez ensuite votre Dockerfile pour copier php.ini au bon emplacement dans le système de fichiers du conteneur :

FROM php:8.2-apache
WORKDIR /var/www/html

COPY php.ini /usr/local/etc/php/php.ini
COPY index.php .

Le niveau de signalement des erreurs de PHP peut devoir être modifié pour une utilisation en production. Les erreurs de faible gravité, comme les notifications et les avertissements de dépréciation, peuvent rapidement remplir les journaux d’informations inutilisables. Vous pouvez définir cette valeur de façon dynamique au début de votre script en fonction d’une variable d’environnement :

if (getenv("IS_PRODUCTION")) {
    // Disable error reports which are not an immediate issue
    error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
}

5. Exécuter votre conteneur PHP avec un utilisateur non root

Par défaut, Docker exécute les conteneurs en tant que root. Le processus exécuté dans le conteneur dispose des mêmes privilèges que l’utilisateur root sur votre hôte. Si le conteneur est compromis et que son isolation est contournée, des processus malveillants peuvent exécuter des commandes arbitraires sur votre hôte.

Exécuter votre conteneur avec un utilisateur dédié non root contribue à atténuer ces risques. Utilisez l’instruction USER dans votre Dockerfile pour basculer vers un autre utilisateur à partir de ce point :

FROM php:8.2-apache
WORKDIR /var/www/html
USER appuser

COPY index.php .

6. Configurer des vérifications d’état pour votre conteneur PHP

Docker intègre un mécanisme de vérification d’état qui détecte les défaillances des conteneurs. Vous pouvez configurer Docker pour exécuter automatiquement une commande de vérification toutes les trente secondes. Si la commande se termine avec le code 0, le conteneur est considéré comme sain. En revanche, un code de sortie 1 indique que l’application a échoué.

Vous pouvez configurer des vérifications d’état pour vos applications à l’aide de l’instruction HEALTHCHECK dans votre Dockerfile. Pour un service PHP, vous pouvez utiliser curl pour envoyer une requête réseau à l’un des points de terminaison de votre application. Si curl se termine avec le code 0, cela signifie qu’il a reçu une réponse dont le code d’état HTTP appartient à la plage 2xx (succès) :

FROM php:8.2-apache
WORKDIR /var/www/html
USER appuser

HEALTHCHECK CMD curl --fail http://localhost/index.php || exit 1
COPY index.php .

L’état de santé de vos conteneurs s’affiche dans la sortie de la commande docker ps :

$ docker ps
CONTAINER ID	IMAGE  		COMMAND              		CREATED    	STATUS
335889ed4698	php-demo-app		"docker-php-entrypoi…"   	2 mins ago 	Up 2 mins (healthy)

Après l’échec d’une vérification d’état, le conteneur apparaît comme unhealthy. Les orchestrateurs de conteneurs, comme Kubernetes, utilisent ce signal pour redémarrer ou remplacer automatiquement les conteneurs défaillants.

7. Limiter le nombre de ports utilisés

Évitez d’exposer des ports inutiles sur vos conteneurs. Seuls les ports qui doivent être accessibles depuis des processus externes doivent être exposés. Il s’agit des ports auxquels se connecteront les autres conteneurs et les utilisateurs publics.

Pour les applications PHP, il s’agit souvent du port 80 lorsqu’elles utilisent une image intégrant un serveur web, comme php:8.2-apache. Les images basées sur FPM doivent plutôt exposer le port 9000 afin qu’un conteneur de serveur web distinct puisse s’y connecter. Le port FPM ne doit pas être accessible publiquement.

Soyez également vigilant quant aux informations que vous exposez par le biais des variables d’environnement. Supprimez les variables inutilisées pour réduire les risques de divulgation. Des outils comme docker inspect et tout logiciel malveillant présent dans le conteneur peuvent toujours accéder à la liste complète des variables que vous avez définies.

8. Mettre régulièrement à jour les dépendances

Tous les composants de votre pile doivent être régulièrement mis à jour pour bénéficier des corrections de bugs et des mises à jour de sécurité. Cela concerne notamment :

  • Les mises à jour du système d’exploitation de base dans votre image Docker.

  • Les mises à jour de la version de PHP et des extensions que vous utilisez.

  • Les packages Composer installés en tant que dépendances dans votre projet.

Ces mises à jour sont essentielles pour vous protéger des nouvelles vulnérabilités. Reconstruisez votre image avec docker build --pull à chaque compilation pour obtenir les mises à jour. Cette commande récupère la dernière version de votre image de base, y compris les packages du système d’exploitation et les versions de PHP mis à jour.

Vous devriez également exécuter régulièrement composer update dans votre projet afin d’installer les derniers correctifs pour vos dépendances. Des outils comme Snyk vous permettent de repérer les dépendances obsolètes ou vulnérables et vous indiquent comment les mettre à jour vers une version corrigée. Après avoir mis à jour vos packages avec Composer, reconstruisez votre image Docker afin que vos déploiements en production utilisent les nouvelles versions.

9. Limiter les capacités du conteneur au strict nécessaire

Les capacités du noyau Linux limitent les actions potentiellement sensibles que les processus peuvent effectuer. Docker exécute déjà les conteneurs avec un ensemble de capacités non privilégiées, mais celles-ci restent excessives pour les charges de travail PHP classiques. Par exemple, les paramètres par défaut permettent aux processus du conteneur de modifier les permissions des fichiers, d’arrêter des processus et de manipuler les ID utilisateur.

Vous pouvez ajouter ou supprimer des capacités du conteneur avec les options --cap-add et --cap-drop de docker run. Pour une sécurité optimale, supprimez toutes les capacités avec --cap-drop=all, puis rétablissez uniquement celles dont votre application a besoin.

L’exemple suivant n’accorde au conteneur que la capacité CHOWN. Le conteneur peut modifier le propriétaire des fichiers et des dossiers, mais ne pourra effectuer aucune autre action privilégiée :

$ docker run -d --cap-drop=all --cap-add=CHOWN php-app:latest

10. Utiliser des builds multi-étapes pour séparer la compilation de l’exécution

Les builds multi-étapes facilitent l’écriture et la maintenance de pipelines de compilation plus complexes. Ils permettent d’utiliser plusieurs images de base pendant le processus de compilation tout en gardant une image de sortie légère et précise.

Le modèle multi-étapes est idéal pour les Dockerfiles PHP, car vous souhaiterez généralement installer les dépendances Composer pendant la compilation. Les images Docker officielles de PHP n’incluent pas Composer : le projet publie ses propres images. Avec un build multi-étapes, vous pouvez facilement référencer le binaire Composer dans votre Dockerfile :

FROM php:8.2-apache
WORKDIR /var/www/html

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY composer.json .
COPY composer.lock .
RUN composer install --no-dev
RUN rm /usr/bin/composer

COPY index.php .

L’image composer:2 sera supprimée une fois le build terminé. Cette approche maintient une séparation nette entre la compilation et l’exécution, sans augmenter la taille de l’image finale.

Détecter et corriger les vulnérabilités avec Snyk

Docker peut renforcer la sécurité en isolant dans une certaine mesure vos applications PHP de leurs environnements hôtes. Toutefois, les images de conteneurs comportent souvent des vulnérabilités de sécurité dues à des packages système obsolètes et à des dépendances Composer :

Bannière principale du site Snyk avec le titre « Plébiscité par les développeurs, reconnu par les équipes de sécurité », une interface d’analyse de sécurité et une illustration stylisée du chien Snyk

Analyser votre image à la recherche de vulnérabilités est une étape essentielle avant son déploiement en production. Vous pouvez utiliser Snyk pour analyser votre image Docker PHP, détecter les vulnérabilités et les corriger. La Snyk Vulnerability Database recense les vulnérabilités de tous les systèmes d’exploitation et dépendances populaires, notamment les packages PHP publiés sur Packagist.

Pour commencer à détecter les vulnérabilités avec Snyk, créez un fichier PHP simple et enregistrez-le sous le nom index.php :

<?php

echo "Hello World";

?>

Écrivez ensuite un Dockerfile pour votre application :

FROM php:8.2-apache
WORKDIR /var/www/html

COPY index.php

Créez l’image avec Docker :

$ docker build -t php-app:latest .

Rendez-vous ensuite sur le site de Snyk et cliquez sur Commencer gratuitement pour créer votre compte. Suivez les étapes de configuration, puis installez la CLI Snyk. Vous devez également avoir installé Node.js et npm :

$ npm install -g snyk

Exécutez ensuite la commande snyk auth. Un nouvel onglet de navigateur s’ouvrira pour vous permettre de vous connecter à Snyk et d’authentifier la CLI. Retournez ensuite dans votre terminal.

Pour analyser votre image, exécutez la commande suivante :

$ snyk container test php-app:latest

L’analyse peut prendre quelques instants. Les résultats s’afficheront ensuite dans votre terminal. Vous verrez la liste de toutes les vulnérabilités détectées, avec notamment le package concerné, les versions vulnérables et un lien pour en savoir plus :

Rapport de terminal affichant 110 vulnérabilités dans une image Docker PHP Apache, dont deux problèmes critiques, et recommandant des images de base alternatives.

Le récapitulatif à la fin indique le nombre total de problèmes détectés et suggère des images de base de remplacement qui présentent moins de vulnérabilités.

Corriger les vulnérabilités détectées

Si des vulnérabilités sont détectées, évaluez chacune d’elles pour déterminer si vous devez agir. Une vulnérabilité ne pose pas forcément problème dans votre cas d’utilisation. Commencez par corriger celles de gravité critique, élevée et moyenne.

Pour commencer, le plus efficace consiste souvent à reconstruire votre image avec une image de base mise à jour. Les images communautaires populaires, comme php:8.2-fpm, sont régulièrement republiées dans de nouvelles versions intégrant des correctifs de vulnérabilités. Une fois votre image reconstruite, relancez l’analyse pour vérifier l’évolution de votre score.

Le rapport peut répertorier de nombreuses vulnérabilités dans des composants bas niveau du système d’exploitation. Envisagez d’utiliser une image de base plus petite et plus minimaliste pour réduire votre surface d’attaque globale. Les variantes Alpine Linux, comme php:fpm-alpine, contiennent beaucoup moins de packages, et donc souvent moins de vulnérabilités.

Au moment de la rédaction de cet article, l’image php:8.2-fpm contient, par exemple, 98 vulnérabilités détectées :

Sortie du terminal montrant l’analyse d’une image Docker PHP 8.2, qui a détecté 98 vulnérabilités dans les dépendances : 1 critique, 2 élevées, 1 moyenne et 94 faibles.

La variante php:8.2-fpm-alpine n’en compte que 10, car elle contient beaucoup moins de packages du système d’exploitation :

Sortie du terminal montrant une image Docker PHP 8.2 Alpine présentant 10 vulnérabilités dans ses dépendances, de gravité critique, élevée, moyenne ou faible.

Il se peut que certaines vulnérabilités ne puissent pas être corrigées dans votre projet. Par exemple, les problèmes touchant des packages du système d’exploitation peuvent ne pas affecter votre charge de travail, ou aucun correctif n’est peut-être disponible. Vous pouvez ignorer une vulnérabilité dans un rapport Snyk en transmettant son ID à la commande snyk ignore. Pour trouver cet ID, consultez la fin de l’URL de la vulnérabilité, par exemple SNYK-ALPINE317-CURL-3320725 dans https://security.snyk.io/vuln/SNYK-ALPINE317-CURL-3320725 :

$ snyk ignore --id=SNYK-ALPINE317-CURL-3320725

Enfin, n’oubliez pas que la sécurité des conteneurs repose sur plusieurs éléments. Vous avez besoin d’une image de base sécurisée, d’une version à jour et prise en charge de l’environnement d’exécution PHP, ainsi que de versions corrigées de vos dépendances, des composants de votre chaîne d’outils et du code de votre application. Analysez régulièrement votre image à la recherche de vulnérabilités pour connaître votre niveau de sécurité.

En résumé

À la fin de cet article, vous devriez disposer d’une image Docker prête pour la production, destinée à exécuter votre application PHP. Ce guide présente quelques bonnes pratiques essentielles pour rédiger votre Dockerfile et utiliser les images que vous créez : choisir une image de base précise, utiliser des builds multiétapes pour installer les dépendances et rechercher les vulnérabilités afin de vérifier que vos charges de travail peuvent être déployées en production.

Pour obtenir d’autres conseils sur les images Docker et PHP, consultez la documentation de Docker et les autres articles du blog Snyk.

Snyk est une plateforme de sécurité pensée pour les développeurs et destinée à toutes les personnes chargées de sécuriser le code. Elle peut analyser vos images Docker afin de détecter les vulnérabilités dans vos dépendances, les packages du système d’exploitation et le code PHP. Snyk propose également un plugin pour IDE qui effectue une analyse statique pour détecter les vulnérabilités dès leur apparition. Inscrivez-vous dès maintenant pour détecter et corriger automatiquement les vulnérabilités dans vos images de conteneur PHP.

La sécurité des conteneurs, pensée pour les développeurs

Snyk détecte et corrige automatiquement les vulnérabilités dans les images de conteneurs et les workloads Kubernetes.