Docker pour vos projets Symfony

Tanguy Dechiron

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 .

Lien vers le projet final.



A propos de l'auteur

Tanguy Dechiron

Tanguy Dechiron

Développeur web fullstack (Symfony++).
Passionné de littérature fantasy, jeux de société.
Cycliste du dimanche.


Blog Comments powered by Disqus.