Quand on est développeur, on a parfois besoin de travailler sur des versions de logicielles différentes, ou même avec des technologies différentes. Ceci ne concerne pas uniquement les développeurs fullstack mais, quand on veut bien faire les choses il est parfois intéressant d’avoir sur son poste le backend et le frontend, et ne pas travailler uniquement Postman pour faire les appels api.

Ceci implique d’installer toutes les versions et les logiciels nécessaires et certes oui il est possible de changer de versions par exemple pour PHP

sudo update-alternatives --config php

qui donnera

Il existe 3 choix pour l'alternative php (qui fournit /usr/bin/php).

  Sélection   Chemin           Priorité  État
------------------------------------------------------------
  0            /usr/bin/php8.4   84        mode automatique
  1            /usr/bin/php8.1   81        mode manuel
* 2            /usr/bin/php8.2   82        mode manuel
  3            /usr/bin/php8.4   84        mode manuel

ou avec NodeJS

nvm use 20.19.1

sauf qu’il pourrait y avoir plusieurs projets avec des versions différentes de PHP ou NodeJS sur lesquels travailler et dans ce cas c’est assez compliqué.

Création de containers pour PHP

Disclaimers
Ce qui sera décrit ci-dessous, n’est qu’une suggestion d’architecture et de configuration et n’a pour objet que de montrer qu’un exemple.

Voici l’arborescence :

├── application
│   └── public
│       └── index.php
├── .docker
│   └── php
│       ├── custom.ini
│       └── Dockerfile-dev
├── docker-compose-dev.yml
├── .env.docker
└── Makefile
  • application : dossier dans lequel la source sera placée
  • .docker : toutes la configuration relative aux différents containters (exemple php)
  • docker-compose-dev.yml : fichier de définition des services
  • .env.docker : variables d’environnements utilisées pour la création des containers
  • Makefile : défintion des commandes d’exécution afin de faciliter l’utilisation de docker

.env.docker

CONTAINER_PHP=my_container_php
PHP_IMAGE=php:8.2-alpine
PHP_PORT=9002
DOCKER_SUBNET=172.20.0.0/24
DOCKER_PHP_IP=172.20.0.5
DOCKER_USER=www-data
DOCKER_PROJECT_PATH=/var/www

docker-compose-dev.yml

networks:
    mynetwork:
        ipam:
            config:
                - subnet: ${DOCKER_SUBNET}

services:
    php:
        build:
            context: ./.docker/php/
            dockerfile: Dockerfile-dev
            args:
                UID: ${UID:-1000}
                GID: ${GID:-1000}
                PHP_IMAGE: ${PHP_IMAGE}
                DOCKER_USER: ${DOCKER_USER:-app_user}
                DOCKER_PROJECT_PATH: ${DOCKER_PROJECT_PATH}
        container_name: '${CONTAINER_PHP}'
        platform: linux/amd64
        working_dir: ${DOCKER_PROJECT_PATH}
        user: ${DOCKER_USER:-app_user}
        tty: true
        stdin_open: true
        networks:
            mynetwork:
                ipv4_address: ${DOCKER_PHP_IP}
        ports:
            - '${PHP_PORT:-9000}:9000'
        extra_hosts:
            - 'host.docker.internal:host-gateway'
        volumes:
            - ${PWD:-./}/.docker/php/custom.ini:/usr/local/etc/php/php.ini:ro
            - ${PWD:-./}/application:${DOCKER_PROJECT_PATH}:cached
        restart: unless-stopped

Définition du network

Afin de ne pas se poser de question, et de maîtriser les containers, il semble utile de définir le réseau qui sera utilisé par les différents containers.
Bien sûr que ce n’est pas obligatoire et qu’un simple docker inspect <container_name> permet de connaître l’ip utilisée par le container, mais le définir en amont permet d’avoir toujours la même ip même si le container est détruit.

Défintion des services

Parmi les éléments importants il faut noter :

  • Définition du user.
    Il est courant de voir des exemples qui utilisent l’utilisateur root, c’est à ranger dans le même dossier « NE FAITES PAS ÇA CHEZ VOUS » que chmod 0777.
    Dans la configuration ci-dessus, on définit le user à www-data mais ça pourrait être n’importe quel nom.

.docker/php/Docker-dev

ARG PHP_IMAGE

FROM ${PHP_IMAGE:?"YOU_MUST_DEFINE_PHP_IMAGE_AS_ENV_VAR"}

ARG UID
ARG GID
ARG DOCKER_USER
ARG DOCKER_PROJECT_PATH

RUN docker-php-source extract \
   # do important things \
   && docker-php-source delete \
   && apk --no-cache update && apk --no-cache upgrade

RUN apk --no-cache update && apk --no-cache upgrade && apk --no-cache add \
    acl \
    bash \
    nano \
    shadow \
    sudo \
    zip

# install gnu-libiconv and set LD_PRELOAD env to make iconv work fully on Alpine image.
# see https://github.com/docker-library/php/issues/240#issuecomment-763112749
ENV LD_PRELOAD=/usr/lib/preloadable_libiconv.so

COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions \
    @composer \
    apcu \
    bcmath \
    bz2 \
    intl \
    memcached \
    opcache \
    xsl \
    yaml \
    zip

RUN groupmod -g $GID $DOCKER_USER && usermod -u $UID $DOCKER_USER

RUN mkdir -p /usr/local/var/run && \
    mkdir -p $DOCKER_PROJECT_PATH/var && \
    chown -R $DOCKER_USER: /usr/local/var/run $DOCKER_PROJECT_PATH

WORKDIR $DOCKER_PROJECT_PATH

USER $DOCKER_USER

Le Dockerfile contient les éléments nécessaires à la création du container comme les dépendences au système bash par exemple, ainsi que les librairies PHP et également composer qui permettra de créer plus simplement le projet, ses dépendences.

RUN groupmod -g $GID $DOCKER_USER && usermod -u $UID $DOCKER_USER c’est ici qu’on crée l’utilisateur par défaut du container et ainsi on évite d’utiliser root. On en profite pour créer les répertoires de l’application et on crée le bon propriétaire.

custom.ini

opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
memory_limit=256M
max_execution_time=30
display_errors=Off
error_reporting=E_ALL & ~E_NOTICE
date.timezone=Europe/Paris
post_max_size=10M
upload_max_filesize=2M
short_open_tag=Off

custom.ini permet de configurer PHP et sera utilisé dans le php.ini.

Makefile

make est un outil d’automatisation de tâches.

Installation sous linux

sudo apt-get install make

Installation sous MACOS

brew install make

Fichier Makefile

ifneq (,$(wildcard ./.env.docker))
    include .env.docker
    export
endif

ifeq (, $(which -v docker-compose))
	DOCKER_COMPOSE = docker compose -f docker-compose-dev.yml
else
	DOCKER_COMPOSE = docker-compose -f docker-compose-dev.yml
endif

MAKEFLAGS += --always-make
ARGS ?= $(strip $(subst ',\\',$(subst ",\\",$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)))))

# Misc
.DEFAULT_GOAL = help
.PHONY    = help up down logs composer php sh console cc

## -- Docker Node Makefile --
help: ## Outputs this help screen
	@grep -E '(^[a-zA-Z0-9_-]+:.*?##.*$$)|(^##)' $(firstword $(MAKEFILE_LIST)) | \
		awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'

## -- Docker containers --
.PHONY = up
up: ## Build the images and start the containers
	@$(DOCKER_COMPOSE) up -d --build

.PHONY = down
down: ## Stop the docker hub
	@$(DOCKER_COMPOSE) down --remove-orphans

.PHONY = start
start: ## Start containers
	@$(DOCKER_COMPOSE) start

.PHONY = stop
stop: ## Stop containers
	@$(DOCKER_COMPOSE) stop

## -- Composer --
.PHONY = composer
composer: ## Run composer
	@docker exec -ti $(CONTAINER_PHP) composer $(ARGS)

## -- PHP --
.PHONY = php
php: ## Run php command line
	@docker exec -ti $(CONTAINER_PHP) php $(ARGS)

.PHONY = server-start
server-start: ## Start PHP server
	@docker exec -t $(CONTAINER_PHP) php -S 0.0.0.0:9000 -t public/ > /dev/null &

.PHONY = server-stop
server-stop: ## Stop PHP server
	@docker exec -t $(CONTAINER_PHP) bash -c "ps auxw | grep 'php -S' | \
		grep -v grep | awk '{print \$$1}' | xargs kill"

## -- Shell --
.PHONY = shell
shell: ## Connect to php container
	@docker exec -ti $(CONTAINER_PHP) bash

.PHONY = shell-root
shell-root: ## Connect to php container
	@docker exec -ti -u root $(CONTAINER_PHP) bash

## -- Logs --
.PHONY = logs
logs: ## Show live logs
	@$(DOCKER_COMPOSE) logs --tail=0 --follow

# Avoid to build argument as a target
%::
	@true

La commande make affichera l’aide suivante :

## -- Docker Makefile --  
help                           Outputs this help screen
## -- Docker containers --     
up                             Build the images and start the containers
down                           Stop the docker hub
start                          Start containers
stop                           Stop containers
## -- Composer --              
composer                       Run composer
## -- PHP --                   
php                            Run php command line
server-start                   Start PHP server
server-stop                    Stop PHP server
## -- Shell --                 
shell                          Connect to php container
shell-root                     Connect to php container
## -- Logs --                  
logs                           Show live logs

Then, you have to build the container.

make up

Once it is build, you can stop it

make stop

and start it again

make start

And now Start server

make server-start

and you will be able to see configuration at http://localhost:<PHP_PORT>

Code source : https://github.com/mrbinr/blog-resources/tree/post/learning-docker-php-example/learning-docker-php-example