J'ai voulu utiliser Docker pour l'environnement de mon dernier projet professionnel sous Symfony, et je me suis tout naturellement dirigé vers le projet officiellement supporté dans la documentation.
Il a l'avantage d'être très complet et bien décrit, j'ai pu donc le modeler pour qu'il corresponde à mes besoins.
Bon si tout était parfait je pourrai m'arrêter là 😉 mais je trouve à ce projet quelques défauts pour mon usage:
- les fichiers Dockerfile, Docker Compose et de configuration de l'environnement sont directement dans le projet Symfony
- il n'y a pas de guideline pour la gestion utilisateur dans les containers, j'ai fini par mettre root un peu partout pour que ça fonctionne ce qui n'est pas vraiment une bonne pratique 😅
- il manque node + npm/yarn ce qui oblige à l'installer et gérer les versions directement sur mon poste
- le projet en lui même est trop complexe pour être modifié facilement (je trouve Docker assez fragile dans sa configuration, une modification a vite fait de tout faire planter)
J'ai donc cherché (encore et encore) afin de trouver un projet plus en adéquation avec ce que je recherche, et j'ai fini par trouver ce projet, qui m'a servi de base pour la suite de cet article.
Description du projet
Ici le projet est beaucoup plus épuré, les Dockerfile du serveur web (nginx) et de php-fpm sont séparés, et tout est ordonnancé via Docker Compose. De plus Node et npm sont inclus dans le container php-fpm, et Symfony est installé dans un dossier app/ séparé du projet Docker 👍
Bon étant perfectionniste, je vais forker le projet et lui ajouter:
- une gestion de l'utilisateur permettant d'éviter les soucis en dev et en prod
- un fichier Makefile pour lancer les différentes commandes plus facilement
Gestion de l'utilisateur dans les containers Docker
Ici le projet démarre les containers non pas sur root mais sur nobody. Sans doute mieux mais pas non plus l'idéal à mon sens.
La première chose à faire est de retirer dans docker/nginx/nginx.conf et docker/php/php-fpm.conf les références à l'utilisateur nobody:
# nginx.conf
user nobody; <-- on supprime cette ligne
worker_processes auto;
pid /run/nginx.pid;
...
# php-fpm.conf
[app]
user=nobody <-- on supprime cette ligne
group=nobody <-- on supprime cette ligne
listen=0.0.0.0:9000
pm=dynamic
...
En faisant ça nginx et php-fpm démarreront avec leurs utilisateurs par défaut respectifs: nginx et www-data.
Etant donné que nginx n'aura pas de fichier à écrire sur le projet Symfony, nous en avons terminé ici. Par contre il faut pouvoir donner la possibilité à l'utilisateur de php-fpm - www-data - de lire et d'écrire dans tout le dossier app/.
Pour ce faire, nous allons utiliser 2 stratégies:
- Donner à l'utilisateur php-fpm les mêmes ids que l'utilisateur de l'OS, afin de faciliter le développement (ça permettra entre autre de créer des fichiers dans son IDE et que le container les reconnaisse comme appartenant à php-fpm, et inversement). Cette étape ne peut se faire qu'au build.
- Mettre à jour à chaque démarrage du container php-fpm le dossier app/ pour qu'il appartienne à l'utilisateur php-fpm
Première étape
On va modifier ici le Dockerfile de php-fpm:
FROM php:8.2-fpm-alpine3.17
# define user to map
ARG USER_ID
ARG GROUP_ID
RUN apk add --update nodejs npm yarn
# add usermod / groupmod utils
RUN apk add shadow
...
WORKDIR /var/www/app
# Map www-data to OS user
RUN if [ ${USER_ID:-0} -ne 0 ] && [ ${GROUP_ID:-0} -ne 0 ]; then \
usermod --uid ${USER_ID} www-data && \
groupmod --gid ${GROUP_ID} www-data \
;fi
EXPOSE 9000
CMD ["php-fpm", "-F", "-R"]
Puis on modifie le docker-compose.yml afin de pouvoir passer le USER_ID et GROUP_ID en paramètre lors du build de l'image php-fpm:
version: '3.7'
services:
php:
build:
context: './docker/php'
dockerfile: Dockerfile
args:
USER_ID: ${USER_ID:-0}
GROUP_ID: ${GROUP_ID:-0}
Et enfin on créé un fichier .env pour configurer ces ids (on les retrouve via la commande id -u && id -g, pour moi c'est 1000 et 1000 par ex):
USER_ID=0
GROUP_ID=0
Il ne faut mettre les ids qu'en développement et laisser à 0 en production
Deuxième étape
Maintenant on veut mettre à jour le dossier app/ à chaque démarrage du container.
Pour cela on créé un fichier docker/php/docker-entrypoint.sh:
#!/bin/sh
chown -R www-data:www-data /var/www/app
# Execute default entrypoint
docker-php-entrypoint $@
Puis on met à jour le Dockerfile de php-fpm:
...
COPY ./docker-entrypoint.sh /usr/local/bin/docker-entrypoint
RUN chmod +x /usr/local/bin/docker-entrypoint
WORKDIR /var/www/app
# Map www-data to OS user
RUN if [ ${USER_ID:-0} -ne 0 ] && [ ${GROUP_ID:-0} -ne 0 ]; then \
usermod --uid ${USER_ID} www-data && \
groupmod --gid ${GROUP_ID} www-data \
;fi
EXPOSE 9000
ENTRYPOINT [ "docker-entrypoint" ]
CMD ["php-fpm", "-F", "-R"]
Et enfin je mets à jour le fichier docker-compose.yml afin de démarrer php-fpm avant nginx (grâce à la directive depends_on):
version: '3.7'
services:
...
nginx:
build: './docker/nginx'
ports: ['80:80']
volumes: ['./app/:/var/www/app:cached']
depends_on:
- php
Création du fichier Makefile
Alors là je ne me suis pas foulé, j'ai repris le Makefile du project Docker officiel de Symfony, en ne conservant que les commandes essentielles, sans commentaires:
build:
docker-compose build --pull --no-cache
up:
docker-compose up --detach
start: build up
down:
docker-compose down --remove-orphans
logs:
docker-compose logs --tail=0 --follow
sh:
docker-compose exec --user www-data php sh
Et voilà ! Pour build, démarrer les containers et installer Symfony :
# Génération des images
make build
# Démarrage des containers
make up
# Puis on se place dans le container php-fpm
make sh
# Installation de Symfony:
composer create-project symfony/website-skeleton .
# Pour un microservice, console application console ou API:
composer create-project symfony/skeleton .
A propos de l'auteur
Tanguy Dechiron
Développeur web fullstack (Symfony++).
Passionné de littérature fantasy, jeux de société.
Cycliste du dimanche.