Comment lancer un conteneur PHP avec les bonnes permissions
Si vous rencontrez des erreurs de permissions sur votre conteneur PHP, alors cet article est fait pour vous et vous découvrirez comment faire pour résoudre votre problème.
Si vous rencontrez des erreurs de permissions sur votre conteneur PHP, il y a de fortes chances que ce soit lié à une mauvaise configuration de l'image qui lance le conteneur.
Cela peut arriver par exemple si vous avez installé des dépendances en dehors de votre conteneur, sur votre terminal, et que vous avez lancé un conteneur PHP sur votre projet après avoir déjà créé quelques fichiers.
Dans ce genre de cas, il y a une incohérence entre l'utilisateur de votre système d'exploitation, qui est bien souvent un utilisateur non privilégié (autre que root
), et l'utilisateur de votre conteneur qui, si vous n'avez rien fait de spécial dans la configuration de votre image, sera par défaut l'utilisateur root
.
Comment savoir si je suis root
ou un autre utilisateur ?
Cette astuce n'est pas utilsable sur Windows, car la gestion des permissions sur Windows est bien différente par rapport à un système d'exploitation GNU/Linux ou Mac OS X.
Pour savoir si vous êtes un autre utilisateur que root
, il vous suffit de taper la commande suivante dans un terminal (en dehors de votre conteneur Docker).
id
Cette commande aura pour but de vérifier quel est l'utilisateur actuellement connecté (et sur lequel vos commandes seront actives).
Vous aurez normalement quelque chose comme cela si vous n'êtes pas sur un compte administrateur.
uid=1000(amin) gid=1000(amin) groupes=1000(amin),963(docker),1001(sudo)
Ici, on peut voir notamment que notre utilisateur a l'identifiant 1000
, ce qui signifie généralement que nous sommes sur un compte utilisateur.
Les comptes utilisateurs commenent généralement à partir de l'identifiant 1000
, ça tombe bien, ici nous sommes connecté avec le premier utilisateur donc.
Si nous étions sur un compte administrateur, nous aurions eu quelque chose qui ressemblerait à ceci.
uid=0(root) gid=0(root) groupes=0(root)
On voit bien ici que l'identifiant a changé, et qu'il est passé de 1000
à 0
. C'est normal, cette commande a été lancé en tant qu'utilisateur administrateur (root
), donc la sortie est différente car nous sommes sur un utilisateur différent.
Cette information est très importante, même si elle peut parraître abstraite pour notre problème, car elle nous permet en réalité de comprendre d'où viennent nos erreurs.
Si cette fois-ci nous essayons par exemple de lancer un conteneur Docker avec php
, nous pourrions tout à fait utiliser la même commande (id
) afin de savoir si nous avons la même sortie qu'avec notre utilisateur non-privilégié.
docker run php:8.0.0 id
La commande ci dessus nous permet de télécharger l'image php:8.0.0
si nous ne l'avons pas actuellement sur notre système d'exploitation, de créer un conteneur avec la commande run
et de lancer une commande dans ce conteneur qui sera id
.
Si on essaie, on obtient la sortie suivante.
uid=0(root) gid=0(root) groups=0(root)
Ça ne vous rappelle rien ? Effectivement, c'est la sortie que nous avions lorsque nous avons testé la commande en tant qu'utilisateur administrateur.
Il apparait donc évident ici que l'utilisateur de notre système d'exploitation et l'utilisateur de notre conteneur sont différents.
Mais en quoi cela explique notre problème ?
UNIX et les permissions
Sur un système d'exploitation GNU/Linux ou Mac OS X, les fichiers ont chacuns des permissions bien précises.
Ces systèmes d'exploitation implémentent tous deux l'idée générale des permissions d'un système d'exploitation UNIX, dont ils héritent bien plus que cela d'ailleurs.
Si nous nous amusons à créer un fichier test.txt
sur notre système de fichier, nous pourrions tout à fait avoir la possibilité de savoir avec quelles permissions ce fichier a été créé.
Tout d'abord, créons ce fichier.
touch test.txt
La commande touch
ici nous permet de modifier la date de dernière modfification d'un fichier.
Mais elle permet également de créer le fichier si ce dernier n'existe pas. Pratique dans notre cas car nous ne souhaitons rien n'y ajouter de particulier, simplement de pouvoir comprendre notre problème.
Maintenant, essayons de lancer la commande ls -l
afin d'avoir des informations sur notre fichier, notamment les permissions de ce dernier.
ls -l
La sortie de cette commande sur mon ordinateur me donne ceci.
-rw-r--r-- 1 amin amin 0 31 mai 16:10 test.txt
Cette ligne est intéressante pour deux raisons.
La première, est qu'elle m'indique clairement qui est le propriétaire du fichier. C'est la première occurence du mot amin
qui me l'indique.
Et la deuxième, c'est que je sais exactement ce que je peux faire, ou ne pas faire, avec ce fichier grâce à la toute première partie, ici -rw-r--r--
qui m'indique que je peux lire et écrire le fichier, mais que les autres utilisateurs peuvent simplement lire ce fichier.
Essayons maintenant de faire la même chose, mais cette fois-ci dans un conteneur PHP.
docker run --interactive --tty php:8.0.0 bash
Ici, les options --interactive
et --tty
me permettent toutes deux de m'assurer que le conteneur ne se termine pas lorsque je lançerais la commande bash
car je souhaite envoyer d'autres commandes à l'intérieur.
Notamment les commandes précédentes que nous allons essayer maintenant.
root@823c7a501248:~# touch test.txt
root@823c7a501248:~# ls -l
Nous devrions avoir une sortie avec pleins de fichiers, dont la ligne qui nous intéresse.
-rw-r--r-- 1 root root 0 May 31 14:16 test.txt
Étrange, cela ne ressemble pas à ce que nous avions sur notre système d'exploitation.
En réalité, c'est tout à fait normal, et si vous êtes arrivé jusqu'ici, vous devriez commencer à comprendre quel est le problème ici.
Création de fichier à l'intérieur d'un conteneur
Si vous essayez de créer des fichiers dans un conteneur PHP, ces derniers auront (par défaut) comme propriétaire l'utilisateur root
.
Si on le fait à l'extérieur, dans mon cas, nous aurons comme propriétaire l'utilisateur amin
.
Essayons maintenant de monter un dossier de notre système de fichier, directement dans notre conteneur, et essayons de voir ce qu'il se passe.
Tout d'abord, je créé un conteneur PHP avec un point de montage vers mon dossier courant en provenance de mon système de fichier.
docker run --interactive --tty --volume .:/root --workdir /root php:8.0.0 bash
Cette commande nous permet de lancer un conteneur comme précédemment, sauf que cette fois-ci nous nous déplaçons par défaut dans le dossier /root
grâce à l'option --workdir
et nous montons tous les fichiers de notre système de fichier, dans le dossier dans lequel nous lançons cette commande vers le dossier du conteneur /root
.
Maintenant que cela est fait, nous pouvons refaire la même chose, c'est-à-dire créér un fichier, mais depuis le conteneur. Cela aura pour effet de le créér également dans notre système de fichier. C'est pratique pour ne pas perdre notre travail, ou pour sauvegarder le résultat d'une commande comme composer install
par exemple.
root@823c7a501248:~# touch test.txt
Nous pouvons maintenant sortir du conteneur en tapant exit
.
root@823c7a501248:~# exit
Essayons maintenant de voir ce qu'il se passe si nous lançons la commande ls -l
, mais cette fois-ci sur notre terminal, dans notre système de fichier.
ls -l
Nous avons désormais cette sortie.
-rw-r--r-- 1 root root 0 31 mai 16:22 test.txt
Étrange ! Pourtant nous sommes bien sur notre système d'exploitation, avec notre utilisateur non-privilégié. Comment se fait-il que les permissions sont ici celle de l'utilisateur root
?
Cela s'explique tout simplement car Docker est une solution de conteneurisation, et non pas de virtualisation.
Le système de fichier n'est pas virtualisé, pour grossir le trait Docker utilise une petite astuce qui permet d'isoler une partie de son système de fichier au moment de lancer un conteneur.
Néanmoins, il est possible de lier un conteneur avec notre système de fichier, et de retrouver du lien entre les deux.
Docker ne va créer que le strict nécessaire : les applications nécessaire pour lancer le conteneur Docker sur PHP en version 8.0.0 et rien d'autre.
Ni le CPU, ni la RAM, ni le système de fichier n'est virtualisé ou dupliqué. Tout cela existe déjà et Docker ne va pas se priver de les réutiliser lorsque nécessaire.
Il est donc tout à fait logique qu'un fichier créé dans un conteneur PHP avec l'utilisateur root
garde ses permissions s'il a été créé dans un dossier monté sur notre système de fichier.
Explication du problème
Maintenant que nous avons compris cela, nous comprenons également que n'importe quelle commande qui vient créer un fichier dans un conteneur PHP, monté sur notre système de fichier, va devenir problématique à partir du moment où nous ne faisons pas quelque chose pour changer l'utilisateur qui lance la commande du conteneur.
Car si nous continuons à utiliser root
, il est tout à fait normal d'avoir des dépendances Composer qui s'installent avec les mauvaises permissions.
Voir pire, si vous essayez de créer un contrôleur avec Symfony et ses outils en ligne de commande : vous ne pourrez même pas éditer et sauvegarder vos modification dans ce contrôleur.
Il est donc nécessaire de trouver une solution à ce problème.
Dockerfile
La solution la plus évidente serait de contacter les mainteneurs de l'image PHP en leur expliquant cela. Néanmoins, il y a de forte chance qu'un inconnu comme moi soit tout simplement ignoré, ou bien que cette problématique (dont nous allons apporter une solution juste après) ne soit pas prioritaire puisque justement, il existe une solution alternative à demander une modification de cette image.
Surtout qu'il serait bien dommage de coder en dur un identifiant d'utilisateur dans une image officielle. Qu'est-ce qu'il se passe si mon frère sur mon ordinateur qui a l'identifiant 1001
utilise cette image ? Il aura de nouveau des soucis de permissions, même si PHP résoud ce problème en ajoutant un utilisateur avec les droits 1000
dans le conteneur.
Il existe bien des façons plus complexes de pouvoir également résoudre ce problème, comme par exemple en acceptant des arguments au moment de construire le conteneur. Mais cela peut vite devenir lourd à l'utilisation, surtout pour tester rapidement une image PHP.
Voyons ensemble comment nous, en tant que développeur, nous pouvons apporter une solution à ce problème.
Tout d'abord, il est important de comprendre qu'il existe deux grandes familles d'images utilisées pour l'image PHP, et pas que d'ailleurs.
Les images classiques, qui tournent sur un système d'exploitation qui implémente la librairie glibc
, et les images sur Alpine et qui utilisent la librairie libmusl
.
La différence entre les deux intervient dans les commandes qui vont nous permettre d'administrer ces systèmes d'exploitation. Je vous donnerais les solutions pour ces deux types d'images afin que vous puissiez utiliser celle qui vous correspond le mieux.
Tout d'abord, pour toutes les images, nous aurons besoin de créer un fichier Dockerfile
.
touch Dockerfile
Une fois que cela est fait, nous aurons besoin d'ajouter ces lignes dans ce fichier.
FROM php:8.0.0
RUN groupadd --gid 1000 php
RUN useradd --create-home --uid 1000 --gid 1000 php
La première ligne nous permet de repartir de l'image php:8.0.0
pour notre image personnalisée.
La deuxième nous permet de créer un groupe ayant l'identifiant 1000
. Ajustez l'identifiant de ce groupe par rapport à votre système d'exploitation. Si par exemple, chez vous, la sortie de la commande id
vous donne l'identifiant 1000
, vous devriez plutôt avoir cette ligne.
RUN groupadd --gid 1001 php
Le nom du groupe sera php
, comme pour l'utilisateur à créer.
Enfin, pour la dernière ligne, nous ajoutons un utilisateur php
qui aura les droits d'utilisateur 1000
et le bon identifiant de groupe. Nous nous assurons également qu'il ait bien un dossier utilisateur dans le dossier /home/php
afin de pouvoir plus tard monter des volumes dans un dossier qui contient les bonnes permissions.
Maintenant, il ne nous reste plus qu'à construire l'image. Ici, j'utilise mon propre nom d'utilisateur afin de lui donner un espace de nom, mais vous n'êtes pas obligé.
docker build --tag aminnairi/php:8.0.0 .
Maintenant il ne nous reste plus qu'à tester cette image avec les options que nous avions utilisés précédemment.
docker run --interactive --tty --volume .:/home/php --user php --workdir /home/php aminnairi/php:8.0.0 bash
La comande ici est plus conséquente. Essentiellement, ce qui a changé est que nous avons un dossier de montage du volume différent qui est le dossier /home/php
qui sera le dossier créé pour l'utilisateur php
, ainsi que l'utilisateur à utiliser dans le conteneur qui est php
.
Maintenant nous pouvons essayer de relancer la même commande que tout à l'heure.
php@cf42859ac673:~$ touch test.txt
À savoir qu'avant de lancer cette commande, j'ai pris soin de supprimer l'ancien fichier qui avait été créé avec l'utilisateur root avec cette commande.
sudo rm test.txt
Nous pouvons maintenant nous déconnecter, et nous assurer que les permissons soient les bonnes.
php@cf42859ac673:~$ exit
Listons les permissions du fichier test.txt
afin de nous assurer que tout fonctionne.
ls -l
Nous devrions avoir quelque chose qui ressemble à ceci.
-rw-r--r-- 1 amin amin 0 31 mai 16:42 test.txt
Génial ! Maintenant, chaque fois que nous lancerons une commande avec Docker et l'image PHP, nous n'aurons plus de soucis de permissions.
Cela est pratique si nous devons par exemple monter un projet PHP sur Symfony ou Laravel, mais que nous souhaitons tout de même pouvoir avoir la main sur les fichiers créés, et ce même si nous utilisons des commandes depuis le conteneur.
C'est aussi pratique puisque même si nous n'avons pas composer
d'installé sur notre système d'exploitation, il suffirait de faire la même chose, mais en utilisans cette fois-ci notre conteneur (pour peu que nous ayons pensé à installer Composer dans le conteneur).
Solution pour Alpine
Je vous avais promis de vous donner la solution pour Alpine également, et voici le Dockerfile associé.
FROM php:8.0.0-alpine
RUN addgroup -g 1000 -S php
RUN adduser -h /home/php -G php -g "" -SDu 1000 php
La syntaxe change légèrement car les outils d'administration sont différents entre Alpine et les autres distributions GNU/Linux, mais le résultat est le même et vous pouvez utiliser cette solution également si vous manquez d'espace sur votre serveur lors d'une mise en production future.
Et c'est tout ! J'espère que cela vous aidera à mieux comprendre d'où viennent vos erreurs de permissions sur vos logs ou vos autres fichiers, et que n'aurez plus de souci de permission en utilisant un conteneur PHP à l'avenir !