Logo de Béjean Développement

Docker, Docker-Compose & Magento 2.4

Objectif

Installer Magento 2.4 en utilisant Docker et Docker-Compose.

Sources

Avant de commencer, cela fait un moment que j'utilise Docker pour développer sous Magento 2. Après avoir testé plusieurs solutions, je me suis basé sur la solution proposée par Mark Shust.

J'ai également visionné un paquet de vidéos de la chaîne Xavki.

Tutoriel

Changelog

23/11/2020 :

  • Remplacement de MariaDb 10.4.13 par MySQL 8.0.22

04/08/2020 :

  • Rédaction du tutoriel

Arborescence

Commencer par créer le répertoire de travail et au sein de ce dossier, créer les répertoires suivants :

mkdir -p src/app_data1 src/nginx_log1 src/phpfpm_log1 src/sock_data1 src/mysql_data1 src/mysql_log1 src/redis_data1 src/es_data1 src/es_log1 src/rabbitmq_data1 src/rabbitmq_log1

Tous ces dossiers vont servir pour la persistance des données au sein de chacun de nos conteneurs Docker.

Créer ensuite les dossiers pour stocker les fichiers Dockerfile de chaque conteneur :

mkdir -p docker-images/nginx/1.19 docker-images/php/7.4

Dockerfile

NGINX

Dans le dossier docker-images/nginx/1.19, créer un fichier Dockerfile contenant :

FROM nginx:1.19

RUN groupadd -g 1000 app \
 && useradd -g 1000 -u 1000 -d /var/www -s /bin/bash app
RUN touch /var/run/nginx.pid
RUN mkdir /sock

COPY ./nginx.conf /etc/nginx/
COPY ./default.conf /etc/nginx/conf.d/

RUN mkdir -p /var/www/html
RUN chown -R app:app /etc/nginx /var/www /var/cache/nginx /var/run/nginx.pid /sock

EXPOSE 8000

USER app:app

VOLUME /var/www

WORKDIR /var/www/html

Pour finaliser la partie NGINX, créer un fichier nginx.conf contenant :

# let's assume dual-core machine
worker_processes 2;

error_log /var/log/nginx/error.log debug;
pid /var/run/nginx.pid;

events {
  # this should be equal to value of "ulimit -n"
  # reference: https://www.digitalocean.com/community/tutorials/how-to-optimize-nginx-configuration
  worker_connections 768;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    server_names_hash_bucket_size 64;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    # Permet d'uploader des fichers de 20Mo
    client_max_body_size 20M;

    include /etc/nginx/conf.d/*.conf;
}

Pour terminer, créer un autre fichier default.conf contenant :

upstream fastcgi_backend {
  server unix:/sock/docker.sock;
}

server {
  listen 8000;
  server_name localhost;

  set $MAGE_ROOT /var/www/html;
  include /var/www/html/nginx.conf.sample;
}

PHP

Dans le dossier docker-images/php/7.4, créer un fichier Dockerfile contenant :

FROM php:7.4-fpm-buster

RUN apt-get update && apt-get install -y \
  cron \
  git \
  gzip \
  libbz2-dev \
  libfreetype6-dev \
  libicu-dev \
  libjpeg62-turbo-dev \
  libmcrypt-dev \
  libpng-dev \
  libsodium-dev \
  libssh2-1-dev \
  libxslt1-dev \
  libzip-dev \
  lsof \
  default-mysql-client \
  vim \
  zip

RUN docker-php-ext-configure gd --with-freetype --with-jpeg

RUN docker-php-ext-install \
  bcmath \
  bz2 \
  calendar \
  exif \
  gd \
  gettext \
  intl

RUN apt-get install -y libonig-dev

RUN docker-php-ext-install \
  mysqli \
  opcache \
  pcntl \
  pdo_mysql \
  soap \
  sockets \
  sodium \
  sysvmsg \
  sysvsem \
  sysvshm \
  xsl \
  zip

RUN cd /tmp \
  && curl -O https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_x86-64.tar.gz \
  && tar zxvf ioncube_loaders_lin_x86-64.tar.gz \
  && export PHP_VERSION=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;") \
  && export PHP_EXT_DIR=$(php-config --extension-dir) \
  && cp "./ioncube/ioncube_loader_lin_${PHP_VERSION}.so" "${PHP_EXT_DIR}/ioncube.so" \
  && rm -rf ./ioncube \
  && rm ioncube_loaders_lin_x86-64.tar.gz \
  && docker-php-ext-enable ioncube

RUN pecl channel-update pecl.php.net \
  && pecl install xdebug

RUN docker-php-ext-enable xdebug \
  && sed -i -e 's/^zend_extension/\;zend_extension/g' /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

RUN curl -o /tmp/ssh2-1.2.tgz https://pecl.php.net/get/ssh2 \
  && pear install /tmp/ssh2-1.2.tgz \
  && rm /tmp/ssh2-1.2.tgz \
  && docker-php-ext-enable ssh2

RUN groupadd -g 1000 app \
 && useradd -g 1000 -u 1000 -d /var/www -s /bin/bash app

RUN apt-get install -y gnupg

RUN curl -sS https://getcomposer.org/installer | \
  php -- --version=1.9.0 --install-dir=/usr/local/bin --filename=composer

RUN curl -s https://packages.blackfire.io/gpg.key | apt-key add - \
  && echo "deb http://packages.blackfire.io/debian any main" | tee /etc/apt/sources.list.d/blackfire.list \
  && apt-get update \
  && apt-get install blackfire-agent blackfire-php

RUN printf '* *\t* * *\tapp\t%s/usr/local/bin/php /var/www/html/update/cron.php\n' >> /etc/crontab \
  && printf '* *\t* * *\tapp\t%s/usr/local/bin/php /var/www/html/bin/magento cron:run\n' >> /etc/crontab \
  && printf '* *\t* * *\tapp\t%s/usr/local/bin/php /var/www/html/bin/magento setup:cron:run\n#\n' >> /etc/crontab

COPY ./www.conf /usr/local/etc/php-fpm.d/
COPY ./php.ini /usr/local/etc/php/
COPY ./php-fpm.conf /usr/local/etc/
COPY bin/cronstart /usr/local/bin/

RUN mkdir -p /etc/nginx/html /var/www/html /sock \
  && chown -R app:app /etc/nginx /var/www /usr/local/etc/php/conf.d /sock

USER app:app

VOLUME /var/www

WORKDIR /var/www/html

EXPOSE 9001

Continuer en créant le fichier php-fpm.conf en y intégrant le contenu suivant :

[global]
error_log = /proc/self/fd/2
daemonize = no

[www]
; if we send this to /proc/self/fd/1, it never appears
access.log = /proc/self/fd/2

listen = /sock/docker.sock
listen.owner = app
listen.group = app
listen.mode = 0660

pm = dynamic
pm.max_children = 10
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6

clear_env = no

; Ensure worker stdout and stderr are sent to the main error log.
catch_workers_output = yes

Puis créer le fichier php.ini avec le contenu :

; https://devdocs.magento.com/guides/v2.3/install-gde/prereq/php-settings.html
memory_limit = 4G
max_execution_time = 1800
zlib.output_compression = On
cgi.fix_pathinfo = 0
date.timezone = "Europe/Paris"

xdebug.remote_autostart = 1
xdebug.remote_enable = 1
xdebug.remote_host = host.docker.internal
xdebug.remote_port = 9001
xdebug.idekey = PHPSTORM

opcache.enable_cli = 1
opcache.memory_consumption = 512
opcache.max_accelerated_files = 100000
opcache.save_comments = 1

upload_max_filesize = 20M
post_max_size = 20M

; https://www.cloud-devops.fr/php-cacher-la-version-dans-les-headers/
expose_php = On

L'avant-dernier fichier www.conf doit contenir :

[www]
user = app
group = app
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

CRON

Le conteneur pour les crons est basé sur le conteneur PHP. Seulement pour fonctionner, il a besoin que le contenu ci-dessous soit intégré dans un fichier cronstart situé dans le dossier docker-images/php/7.4/bin. De plus, veiller à ce que ce fichier ait les droits d'éxécution.

#!/bin/bash
service cron start

touch /var/www/html/var/.setup_cronjob_status /var/www/html/var/.update_cronjob_status
chown app:app /var/www/html/var/.setup_cronjob_status /var/www/html/var/.update_cronjob_status

/usr/bin/crontab

auth.json

Afin que les conteneurs puissent se lancer sans erreur, il est nécessaire de stocker les clés d'authentification pour Magento dans le fichier auth.json du dossier docker-images/php/7.4/.composer. Voici le contenu :

{
  "http-basic": {
    "repo.magento.com": {
      "username": "XXXX",
      "password": "XXXX"
    }
  }
}

Bien sûr, il faut remplacer les XXX par vos clés.

Docker-Compose

Créer un premier fichier nommé .env avec le contenu suivant :

NGINX_VERSION=1.19
PHP_VERSION=7.4
MYSQL_VERSION=8.0.22
REDIS_VERSION=6.0.6
ELASTICSEARCH_VERSION=7.6.2
RABBITMQ_VERSION=3.8.5-management

MYSQL_ROOT_PASSWORD=magento
MYSQL_DATABASE=magento
MYSQL_USER=magento
MYSQL_PASSWORD=magento
RABBITMQ_DEFAULT_USER=magento
RABBITMQ_DEFAULT_PASS=magento

Ce fichier permet de définir des variables qui seront utilisée au sein du fichier docker-compose.yml.

Créer le fichier docker-compose.yml en y insérant le contenu ci-dessous :

version: '3'
services:
  nginx:
    build:
      context: ./docker-images/nginx/${NGINX_VERSION}
    ports:
      - 80:8000
    links:
      - phpfpm
      - redis:cache
    volumes:
      - ./src/app_data1:/var/www/html
      - ./src/sock_data1:/sock
      - ./src/nginx_log1:/var/log/nginx
    networks:
      - magento

  phpfpm:
    build:
      context: ./docker-images/php/${PHP_VERSION}
    links:
      - db1
    volumes:
      - ./docker-images/php/${PHP_VERSION}/.composer:/var/www/.composer
      - ./src/app_data1:/var/www/html
      - ./src/phpfpm_log1:/var/log
      - ./src/sock_data1:/sock
    networks:
      - magento

  redis:
    image: redis:${REDIS_VERSION}
    restart: on-failure:5
    volumes:
      - ./src/redis_data1:/data
    expose:
      - 6379
    networks:
      - magento

  cron:
    build:
      context: ./docker-images/php/${PHP_VERSION}
    user: root
    command: /usr/local/bin/cronstart
    tty: true
    links:
      - db1
      - elasticsearch1
      - rabbitmq
    volumes:
      - ./src/app_data1:/var/www/html
      - ./src/sock_data1:/sock
    networks:
      - magento

  db1:
    image: mysql:${MYSQL_VERSION}
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    volumes:
      - ./src/mysql_data1:/var/lib/mysql
      - ./src/mysql_log1:/var/log/mysql
    ports:
      - 3306:3306
    networks:
      - magento

  elasticsearch1:
    image: elasticsearch:${ELASTICSEARCH_VERSION}
    environment:
      - cluster.name=es-cluster
      - bootstrap.memory_lock=true
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms2048m -Xmx2048m"
      - cluster.initial_master_nodes=elasticsearch1
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - ./src/es_data1:/usr/share/elasticsearch/data
      - ./src/es_log1:/var/log/elasticsearch
    networks:
      - magento

  rabbitmq:
    image: rabbitmq:${RABBITMQ_VERSION}
    environment:
      - RABBITMQ_DEFAULT_USER=${RABBITMQ_DEFAULT_USER}
      - RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS}
    ports:
      - 15672:15672
      - 5672:5672
    networks:
      - magento

networks:
  magento:
    driver: bridge

Build et Installation

Pour terminer, lancer la commande ci-dessous pour construire et lancer les conteneurs :

docker-compose build && docker-compose up -d

Une fois les conteneurs lancés, télécharger et créer le projet Magento 2.4 :

docker-compose exec phpfpm composer create-project --repository=https://repo.magento.com/ magento/project-community-edition:2.4 .

Dès que les fichiers ont été téléchargés, vous pouvez lancer l'installation :

docker-compose exec -T phpfpm bin/magento setup:install \
  --db-host=db1 \
  --db-name=magento \
  --db-user=magento \
  --db-password=magento \
  --base-url=http://localhost/ \
  --backend-frontname=system \
  --admin-firstname=Administrateur \
  --admin-lastname=Magento \
  [email protected] \
  --admin-user=administrateur \
  --admin-password=mettreIciVotreMotDePasse \
  --language=fr_FR \
  --currency=EUR \
  --timezone=Europe/Paris \ 
  --use-rewrites=1 \
  --search-engine=elasticsearch7 \
  --elasticsearch-host=elasticsearch1 \
  --elasticsearch-port=9200 \
  --elasticsearch-index-prefix=magento

A cet instant, Magento est installé, avant de vous lancer, je vous invite à lancer les commandes ci-dessous :

Activation de RabbitMQ :

docker-compose exec -T phpfpm bin/magento setup:config:set --no-interaction --amqp-host=rabbitmq --amqp-port=5672 --amqp-user=magento --amqp-password=magento --amqp-virtualhost=/ --consumers-wait-for-messages=1

Activation de Redis :

docker-compose exec -T phpfpm bin/magento setup:config:set --no-interaction --cache-backend=redis --cache-backend-redis-server=redis --cache-backend-redis-db=0

Activation de Redis pour le Full Page Cache :

docker-compose exec -T phpfpm bin/magento setup:config:set --no-interaction  --page-cache=redis --page-cache-redis-server=redis --page-cache-redis-db=1

Activation de Redis pour les sessions :

docker-compose exec -T phpfpm bin/magento setup:config:set --no-interaction --session-save=redis --session-save-redis-host=redis --session-save-redis-log-level=4 --session-save-redis-db=2

Redémarrer les conteneurs :

docker-compose stop && docker-compose up -d

Installation des modules dans la base de données :

docker-compose exec -T phpfpm bin/magento setup:upgrade && docker-compose exec -T phpfpm bin/magento setup:di:compile

Résultat

Vous pouvez visualiser le résultat en lançant votre navigateur à l'adresse : http://localhost/