Installez SuiteCRM dans un docker

Aujourd'hui dans nos tutos techniques, je vous accompagne pas à pas pour installer SuiteCRM dans un Docker. C'est une solution super efficace que nous proposons pour nos clients.

Dans cet article, je vous propose d’installer SuiteCRM dans un Docker en quelques étapes simples. Vous pouvez ainsi mettre en place un CRM puissant et complètement opérationnel en moins d’une heure.

Les bénéfices d’un Docker sont multiples :

  • l’environnement est contrôlé, avec la bonne version de PHP et les dépendances requises ;
  • possibilité d’installer en quelques minutes sur n’importe quel serveur ;
  • il suffit de compléter avec un Docker MariaDB pour fournir une base de données (voir fichier compose.yaml ci-dessous) et vous obtenez un système fonctionnel ;
  • il est facile d’installer d’autres Dockers sur le même réseau local, avec des applications qui peuvent interagir avec SuiteCRM : n8n, mautic, applications dédiées,… Vous pouvez ainsi construire un « stack marketing » entièrement personnalisé, complètement sécurisé et pour un coût très maîtrisé ;
  • si besoin, le Docker peut être déplacé sur un autre serveur en quelques minutes.

Il est quand même souhaitable que vous ayez un peu d’expérience avec la mise en place d’un Docker, le montage des volumes, et la création des fichiers de configuration.

Dans mon cas, les Dockers qui servent des applications web sont placés derrière un reverse proxy Traefik Proxy. Si vous préférez une autre solution, il faut modifier le fichier compose.yaml en conséquence.

De manière générale, il y a plusieurs paramètres qui pourraient être légèrement différents en fonction de votre installation, mais en tout cas, cette configuration est plutôt robuste. Elle fonctionne en production pour moi depuis plusieurs années.

Version de SuiteCRM et mise en place

Le docker que je vous propose permet d’installer SuiteCRM 7.x ou 8.x. À la date de rédaction (début 2024), je teste encore la migration de mes installations de production de la version 7.14.2 vers la version 8.5.

Le docker fournit l’environnement, il ne vous reste qu’à télécharger la version qui vous convient et installer les fichiers dans le volume qui est monté par le fichier docker compose.

SuiteCRM API v8

Une des forces majeures de SuiteCRM est son API, qui permet à d’autres applications d’interagir avec SuiteCRM, par exemple:

  • création ou synchronisation de contacts ou d’autres enregistrements depuis Woo, WordPress ou d’autres applications web ;
  • ajout automatique de notes ou de documents aux contacts ;
  • extraction de données pour traitement par d’autres applications ;

Le fichier entrypoint.sh vérifie à chaque démarrage du docker que les clés numériques pour l’utilisation de l’API sont bien présentes. Si elles n’existent pas, elles sont créées automatiquement. Le docker que je vous propose est donc prêt pour l’interactivité !

Les fichiers à créer

Voici les fichiers dans la structure qui va servir à la construction du docker (vous pouvez adapter le Dockerfile en fonction des endroits où vous placez les différents fichiers) :

.
├── Dockerfile
└── conf
    ├── apache
    │   ├── apache.conf
    │   └── vhost.conf
    ├── cron-suitecrm
    ├── entrypoint.sh
    └── php
        ├── php.conf
        └── php.ini

Le fichier Dockerfile

Le fichier Dockerfile lui-même est basé sur une version donnée de PHP, ce qui permet d’être aligné avec la matrice de compatibilité de SuiteCRM version 7.x et version 8.x. Il contient un serveur Apache qui servira l’application pour le web.

FROM php:8.2-apache

LABEL vendor="The Moto Company / SL Data Tech"
LABEL maintainer="Steph Legrand"

# define timezone
ENV TZ=Europe/Paris
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

EXPOSE 80

RUN apt-get update && apt-get install -y \
    cron \
    openssl \
    unzip \
    zip

# Install additional dependencies
RUN apt-get update && apt-get install -y \
    libicu-dev \
    libcurl4-openssl-dev \
    libmagickwand-dev \
    libpng-dev \
    libzip-dev \
    libxml2-dev \
    libbz2-dev \
    libonig-dev \
    libgmp-dev \
    libldb-dev \
    libldap2-dev \
    libc-client-dev \
    libkrb5-dev \
    && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
    && docker-php-ext-install \
    pdo_mysql \
    gd \
    curl \
    zip \
    xml \
    mbstring \
    bz2 \
    intl \
    gmp \
    opcache \
    soap \
    imap \
    ldap \
    mysqli

# apache config
COPY conf/apache/apache.conf /etc/apache2/conf-available/suitecrm.conf
COPY conf/apache/vhost.conf  /etc/apache2/sites-available/suitecrm.conf

RUN a2enmod rewrite remoteip

RUN a2dissite 000-default \
    && a2ensite suitecrm \
    && a2enconf suitecrm

RUN ln -sf /proc/self/fd/1 /var/log/apache2/access.log && \
    ln -sf /proc/self/fd/1 /var/log/apache2/error.log

# php config
COPY conf/php/php.ini /usr/local/etc/php/conf.d/suitecrm.ini

# cron config
COPY conf/cron-suitecrm /etc/cron.d/suitecrm

# entrypoint config
COPY conf/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

WORKDIR /app/suitecrm

ENTRYPOINT [ "/entrypoint.sh" ]
CMD ["apache2-foreground"]

Le fichier entrypoint.sh

Voici ensuite le fichier entrypoint.sh. Il est exécuté à chaque démarrage du docker. Dans ce cas précis, son rôle est de :

  • vérifier la version de SuiteCRM et appliquer les bonnes permissions aux répertoires et fichiers ;
  • changer le répertoire de base pour Apache en fonction de la version ;
  • générer si besoin les clés SSL pour Oauth2 (pour permettre le fonctionnement de l’API v8) et vérifier les permissions ;
  • enfin, lancer le serveur Apache.

Certains trouveront qu’il est excessif d’exécuter ceci à chaque démarrage, vous pouvez modifier ce fichier comme vous le souhaitez. Pour moi, ça permet d’être certain que les changements apportés manuellement aux modules personnalisés (notamment quelques scripts PHP) ont bien les bonnes permissions.

#!/bin/bash

if [ "$1" = 'apache2-foreground' ]; then
  # this is run only for main docker execution
  echo "[ tmc ] This is a SuiteCRM docker by The Moto Company / SL Daata Tech" > /dev/stdout


  if [ -f "/app/suitecrm/public/index.php" ]; then
    # Scenario 1: SuiteCRM 8 - /app/suitecrm/public/index.php exists

    echo "[ tmc ] running SuiteCRM 8 configuration" > /dev/stdout

    api_root_path="/app/suitecrm/public/legacy/Api"
    echo "[ tmc ] api root path set to " $api_root_path > /dev/stdout

    # web server root is /app/suitecrm/public
    # nothing more to do in apache conf files
    echo "[ tmc ] web server root set " > /dev/stdout

    # set permissions for SuiteCRM 8 directory structure
    echo "[ tmc ] setting permissions for SuiteCRM 8 directories..." > /dev/stdout
    cd /app/suitecrm
    find . -type d -not -perm 2755 -exec chmod 2755 {} \;
    find . -type f -not -perm 0644 -exec chmod 0644 {} \;
    find . ! -user www-data -exec chown www-data:www-data {} \;
    chmod +x bin/console
    echo "[ tmc ] done." > /dev/stdout

  else
    # Scenario 2: SuiteCRM 7 - /app/suitecrm/public/index.php does not exist

    echo "[ tmc ] running SuiteCRM 7 configuration" > /dev/stdout

    api_root_path="/app/suitecrm/Api"
    echo "[ tmc ] api root path set to " $api_root_path > /dev/stdout

    # update apache conf files:
    # Set web server root to /app/suitecrm
    echo "[ tmc ] updating web server root..." > /dev/stdout
    sed -ri 's#DocumentRoot .*#DocumentRoot /app/suitecrm#' /etc/apache2/sites-available/suitecrm.conf
    sed -ri 's#<Directory /app/suitecrm/public>#<Directory /app/suitecrm>#' /etc/apache2/conf-available/suitecrm.conf
    echo "[ tmc ] done." > /dev/stdout

    # set permissions for SuiteCRM 7 directory structure
    echo "[ tmc ] setting permissions for SuiteCRM 7 directories..." > /dev/stdout
    cd /app/suitecrm
    chown -R www-data:www-data .
    chmod -R 755 .
    chmod -R 775 cache custom modules themes data upload
    chmod 775 config_override.php 2>/dev/null
    echo "[ tmc ] done." > /dev/stdout

  fi

  # start cron
  echo "[ tmc ] starting cron " > /dev/stdout
  service cron start

  # Generate SSL keys for OAuth2 if private key does not exist
  if [ ! -f "$api_root_path/V8/OAuth2/private.key" ]; then
    echo "[ tmc ] no SSL keys found - generating SSL keys in " $api_root_path > /dev/stdout
    openssl genrsa -out "$api_root_path/V8/OAuth2/private.key" 2048
    openssl rsa -in "$api_root_path/V8/OAuth2/private.key" \
          -pubout -out "$api_root_path/V8/OAuth2/public.key"
  fi

  # Set ownership and permissions for SSL keys
  if [ -f "$api_root_path/V8/OAuth2/private.key" ]; then
      echo "[ tmc ] setting ownership and permissions for SSL keys in " $api_root_path > /dev/stdout
      chown www-data:www-data "$api_root_path/V8/OAuth2"/*.key
      chmod 600 "$api_root_path/V8/OAuth2"/*.key
  fi

# end instructions that are run only for main docker execution
fi

echo "[ tmc ] init complete. starting apache"  > /dev/stdout

exec "$@"

Les autres fichiers de configuration

Vous pouvez adapter les valeurs dans le fichier php.ini en fonction de votre serveur hôte et de vos besoins en ressources pour SuiteCRM.

php.ini
date.timezone = Europe/Paris

memory_limit = 1G
upload_max_filesize = 512M
post_max_size = 512M
max_execution_time = 600


display_errors = off
log_errors = on
fastcgi_logging = off

catch_workers_output = yes
decorate_workers_output = no

error_log = /proc/self/fd/1
log_level = notice
php.conf
; this file contains additional directives
; currently it does not seems necessary
; global directives go into php.ini --> conf.d/suitecrm.ini

[global]

error_log = /proc/self/fd/2
log_level = notice

[www]

catch_workers_output = yes
decorate_workers_output = no


pm.max_children = 50
pm.start_servers = 15
pm.min_spare_servers = 15
pm.max_spare_servers = 25
pm.max_requests = 500

Les fichiers suivants servent à configurer le serveur Apache. Principalement, il faut dire à Apache qu’il est derrière un reverse proxy, et que le répertoire de base est /app/suitecrm/public (modifié en cas de besoin par entrypoint.sh, suivant la version de SuiteCRM).

Vous pouvez évidemment modifier le nom du serveur.

apache.conf
<Directory /app/suitecrm/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
</Directory>

LogLevel error
ServerTokens Prod
ServerSignature Off
ServerName themoto.company
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 172.16.0.0/12
vhost.conf
<VirtualHost *:80>
  ServerName themoto.company
  ServerAlias *
  # this lets apache know that we're behind a https proxy
  SetEnvIf X-Forwarded-Proto "https" HTTPS=on
  DocumentRoot /app/suitecrm/public
  ErrorLog "/var/log/apache2/error.log"
  LogFormat "%h %l %u %t \"%r\" %>s %b" common
  CustomLog "/var/log/apache2/access.log" common
</VirtualHost>

Enfin, rien de très spécial pour le fichier de configuration cron, qui sert à mettre en place les tâches programmées de SuiteCRM.

cron-suitecrm
* * * * * www-data cd /var/www/suitecrm; php -f cron.php > /dev/null 2>&1

Voilà, tout est en place ! Une fois que tous les fichiers sont créés, il faut compiler le docker avec la commande :

docker build --network=host -t mysuitecrm .

Le fichier compose.yaml

Pour lancer le serveur et accéder à SuiteCRM, il faut ensuite créer un fichier compose.yaml dans un autre répertoire, avec votre « stack » – ici un serveur de base de données, et le docker SuiteCRM.

Les paramètres « traefik » dans « labels » permettent d’utiliser le reverse proxy. C’est évidemment à adapter selon votre installation. Traefik gère automatiquement la récupération des certificats Let’s Encrypt pour le protocole HTTPS. C’est très efficace ! Mais n’oubliez pas de pointer un champ A dans vos enregistrements DNS vers votre serveur pour « your.suitecrm.url »

services:
  suitecrm-db:
    image: mariadb
    container_name: suitecrm-db
    restart: always
    networks:
      - my-network
    environment:
      - MARIADB_ROOT_PASSWORD=your-root-password
      - MARIADB_USER=suite
      - MARIADB_DATABASE=suite
      - MARIADB_PASSWORD=suite-user-password
    volumes:
      - ./mariadb-data:/var/lib/mysql

  suitecrm-app:
    image: mysuitecrm
    container_name: suitecrm-app
    networks:
      - my-network
    restart: always
    environment:
      - SUITECRM_DATABASE_HOST=suitecrm-db
      - SUITECRM_DATABASE_PORT_NUMBER=3306
      - SUITECRM_DATABASE_USER=suite
      - SUITECRM_DATABASE_NAME=suite
      - SUITECRM_DATABASE_PASSWORD=user-password
      - SUITECRM_HOST=your.suitecrm.url
    volumes:
      - ./suitecrm-data:/app/suitecrm
    depends_on:
      - suitecrm-db
    labels:
      - traefik.enable=true
      - traefik.http.routers.suitecrm.rule=Host(`your.suitecrm.url`)
      - traefik.http.routers.suitecrm.entrypoints=websecure
 #    - traefik.http.routers.suitecrm.middlewares=server-whitelist@file

networks:
  prometheus:
    name: my-network
    external: true

Voilà !

Il faut ensuite copier les fichiers d’installation de SuiteCRM dans ./suitecrm-data, puis il ne reste qu’à démarrer le stack avec :

docker compose up -d

(ou docker-compose, avec un tiret, si vous utilisez une version plus ancienne de compose)

Vous avez maintenant une version opérationnelle de SuiteCRM.

Bon travail !