Install SuiteCRM in a Docker

Today in this new technical tutorial, I am going to give you step-by-step guidelines to set up SuiteCRM in a Docker. This is also what we do for our clients, and it works great!

In this post, I am going to guide you through the installation of SuiteCRM in a Docker container in a few simple steps. This will allow you to set up a powerful and fully operational CRM system in less than an hour.

The benefits of Docker installation are numerous:

  • The environment is controlled, with the correct version of PHP and the required dependencies.
  • You can set this up on any server in just a few minutes.
  • Simply add a MariaDB Docker to provide a database (see compose.yaml file below), and you will have a functional system.
  • It is easy to install other Docker containers on the same local network, with applications that can interact with SuiteCRM, such as n8n, Mautic, custom applications, etc. This way, you can build a fully customized, completely secure and very affordable marketing stack.
  • If necessary, the Docker container can be moved to another server in just a few minutes.

It is probably better if you have a basic understanding of Docker, mounting volumes, and creating configuration files before you start though.

In my case, the Docker containers serving web applications are running behind a Traefik reverse proxy. If you prefer another setup, you need to modify the docker compose file accordingly.

There may be slight variations in parameters depending on your installation. Anyway, this configuration is quite robust and has been working in production for me for several years.

SuiteCRM Version and Setup

This Docker setup allows you to install SuiteCRM 7.x or 8.x. At the time of writing (early 2024), I am still testing the migration of my production installations from version 7.14.2 to version 8.5.

The setup provides the environment, and you need to download the version that suits you and install the files in the volume mounted by the docker-compose file.

SuiteCRM API v8

One of the major strengths of SuiteCRM is its API, which allows other applications to interact with SuiteCRM. For example:

  • Creating or synchronizing contacts or other records from Woo, WordPress, or other web applications.
  • Automatically adding notes or documents to contacts.
  • Extracting data for processing by other applications.

Each time the Docker starts, script file checks that the SSL keys for the API are present. If not, they are created automatically. This Docker is ready for interactivity!

Files to Create

Here is the structure tree for the files that are used to build the Docker (feel free to adapt the Dockerfile based on where you place the different files):

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


This Dockerfile is based on a designated version of PHP, so it is consistent with the compatibility matrix of SuiteCRM version 7.x and version 8.x. It includes an Apache server that will serve the web application.

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


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

# 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 \

# 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/ /
RUN chmod +x /

WORKDIR /app/suitecrm

CMD ["apache2-foreground"]


Next is the script. It is executed each time the Docker starts. In this specific case, its role is to:

  • Check the SuiteCRM version and apply the correct permissions to directories and files.
  • Change the base directory for Apache, depending on SuiteCRM version.
  • Generate SSL keys for OAuth2 if needed (to enable API v8) and check permissions.
  • Finally, start the Apache server.

Some may find it excessive to execute this process every time. You can modify this file as you wish. For me, it ensures that changes made to custom modules (especially some PHP scripts) have the correct permissions.


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

    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

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

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

    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


  # 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"

  # 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

# end instructions that are run only for main docker execution

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

exec "$@"

Other configuration files

You can adjust the values in the php.ini file according to your host server and your resource requirements for SuiteCRM.

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
; this file contains additional directives
; currently it does not seems necessary
; global directives go into php.ini --> conf.d/suitecrm.ini


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


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

The following files are used to configure the Apache server. We have to let Apache know that it is sitting behind a reverse proxy and that the base directory is /app/suitecrm/public (modified if necessary by, depending on the SuiteCRM version).

Of course, modify the server name to suit your needs.

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

LogLevel error
ServerTokens Prod
ServerSignature Off
RemoteIPHeader X-Forwarded-For
<VirtualHost *:80>
  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

Finally, nothing special about the cron config file, but it is essential to run scheduled tasks in SuiteCRM:

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

This is it, everything is ready. Now build the Docker with:

docker build --network=host -t mysuitecrm .

compose.yaml file

To launch the server and access SuiteCRM, you need to create a compose.yaml file in another directory, with your stack: a database server, and the SuiteCRM docker.

The traefik parameters in the labels section enable the use of the reverse proxy. It should be adapted to suit your installation. Traefik automatically handles the retrieval of Let’s Encrypt certificates for the HTTPS protocol. It’s very efficient!

Anyway, don’t forget to point an A record in your DNS to your server for “your.suitecrm.url”.

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

    image: mysuitecrm
    container_name: suitecrm-app
      - my-network
    restart: always
      - SUITECRM_DATABASE_HOST=suitecrm-db
      - SUITECRM_DATABASE_PASSWORD=user-password
      - SUITECRM_HOST=your.suitecrm.url
      - ./suitecrm-data:/app/suitecrm
      - suitecrm-db
      - 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

    name: my-network
    external: true


Next, you need to copy the SuiteCRM installation files into ./suitecrm-data, and then all that’s left is to start the stack with:

docker compose up -d

(or docker-compose, with a hyphen, if you are using an older version of compose)

You now have an operational version of SuiteCRM.

Good job!