Установка SuiteCRM на Ubuntu с Docker: рабочий сервер для отдела продаж за один вечер
Установка SuiteCRM на Ubuntu с Docker занимает вечер: VPS, Docker, собственный образ на php:8.3-apache, MariaDB 11.4, установщик, HTTPS через прокси, cron и бэкап. Официального образа у SuiteCRM нет, поэтому ниже сборка по документации и список проверок, без которых запускать отдел рано.
Что понадобится: VPS, домен и версия SuiteCRM
Сначала про версию, потому что от неё зависит весь стек. По матрице совместимости текущая линейка SuiteCRM 8.10 работает на PHP 8.2, 8.3 и 8.4, на MariaDB 10.6, 10.11, 11.4 и 11.8 или MySQL 8.0 и 8.4 и под веб-сервером Apache 2.4. Начиная с 8.7 SuiteCRM не запускается на PHP 7.4, так что старые инструкции из интернета с PHP 7 для восьмой версии не годятся. На момент проверки последним релизом в репозитории был 8.10.2. Я беру PHP 8.3 и MariaDB 11.4: обе в списке поддерживаемых и обе имеют официальные образы.
Теперь про образ. В README SuiteCRM-Core Docker не упоминается, официального образа у проекта я не нашёл. Поэтому стек строится так: официальный php:8.3-apache, в него ставятся расширения PHP из документации, и в образ кладётся пакет SuiteCRM, скачанный с сайта вендора. Свой образ имеет плюс: версия фиксируется в теге, и вы точно знаете, что запущено. Если вам нужно более общее решение для установки open-source продуктов под ключ, у нас есть услуга внедрения open-source.
Что нужно по железу и сети. Небольшой VPS с Ubuntu 22.04 или 24.04 LTS (в документации Docker поддерживаются 22.04, 24.04 и 26.04), публичный IP, домен с A-записью на этот IP и открытые порты 80 и 443. Цену VPS я здесь не называю: она зависит от провайдера и даты, сравнивайте тарифы по памяти, диску и каналу на день заказа. Объём памяти и диска зависит от числа пользователей и вложений, поэтому диск лучше брать с запасом под файлы и копии.
Подготовьте заранее три вещи, иначе вечер превратится в ночь. Пакет SuiteCRM 8.x с сайта вендора (сборку скачивайте на странице загрузки suitecrm.com/download). Пароли для администратора и базы, придуманные заранее и сохранённые в менеджере паролей. И короткий список того, что нужно отделу на старте: роли, воронка, источники лидов. Подробности об остальных возможностях системы я описал в обзоре SuiteCRM.
- VPS с Ubuntu 22.04 или 24.04 LTS и публичным IP.
- Домен с A-записью на этот IP.
- Открытые порты 80 и 443.
- Пакет SuiteCRM 8.x с сайта вендора.
- Пароли администратора и базы в менеджере паролей.
Подготовка Ubuntu и Docker
Docker ставлю по официальной инструкции из документации Docker, из их репозитория, а не из пакета docker.io дистрибутива. Так обновления приходят вместе с docker-compose-plugin, и команда называется docker compose, без дефиса. Вот последовательность из документации для Ubuntu:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run hello-worldЧто делаю до установки самого SuiteCRM. Обновляю систему и включаю автоматические обновления безопасности. Закрываю SSH по ключам и отключаю вход паролем для root. Включаю межсетевой экран и открываю только 22, 80 и 443. И помню про известную особенность Docker: опубликованные порты контейнеров обходят правила UFW, потому что Docker добавляет свои правила в iptables. Поэтому порт приложения публикую только на локальный адрес, 127.0.0.1:8080, и наружу его выставляет прокси. Подробнее об этой ловушке есть в материале про production-практики Docker Compose.
Рабочий каталог проекта делаю один, например /opt/suitecrm. В нём лежат docker-compose.yml, файл .env с паролями (права 600, не в репозитории), подкаталог app для сборки образа и подкаталог backup. Часовой пояс сервера выставляю в московский, потому что cron и журнал событий CRM должны совпадать с рабочим временем отдела, и проверяю синхронизацию времени. Мелочь, но неверное время в логах регулярно портит разбор инцидентов.
Если на сервере уже есть другие сервисы на портах 80 и 443, прокси вынесите на тот сервер, который уже их занимает, и направьте его на 127.0.0.1:8080. Два прокси на одном порту не уживутся, и это единственное место, где мой шаблон придётся поменять.
Compose-стек: веб-сервер, MariaDB и тома без тега latest
Стек состоит из трёх контейнеров: база данных, приложение и воркер фоновых задач. Фоновый воркер появился как отдельное требование в версии 8.10: по документации Messenger Setup фоновые задачи (ручные миграции, массовые выгрузки) обрабатывает Symfony Messenger worker командой messenger:consume internal-async, и без запущенного воркера такие задачи навсегда остаются в статусе Pending. Документация предлагает запускать его через Supervisor, systemd или cron; в Docker его роль играет отдельный сервис compose. Планировщик, о котором пойдёт речь в шестом разделе, запускается отдельно из cron.
Начну с образа приложения. Положите в каталог app пакет SuiteCRM-8.10.2.zip, два файла конфигурации и Dockerfile. Расширения PHP берутся из списка документации: cli, curl, common, intl, json, gd, mbstring, mysqli, pdo_mysql, openssl, soap, xml, zip; imap и ldap опциональны. Часть из них уже встроена в образ php (mbstring, curl, openssl, xml, json), остальные ставятся командами docker-php-ext-install. После сборки выполните docker compose run --rm app php -m и сверьте список: расхождения лучше увидеть сейчас, а не на экране проверки установщика.
FROM php:8.3-apache
RUN apt-get update && apt-get install -y --no-install-recommends \
libicu-dev libpng-dev libjpeg-dev libfreetype-dev libzip-dev libxml2-dev unzip \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" intl gd mysqli pdo_mysql soap zip \
&& a2enmod rewrite \
&& rm -rf /var/lib/apt/lists/*
COPY php-suitecrm.ini /usr/local/etc/php/conf.d/suitecrm.ini
COPY suitecrm.conf /etc/apache2/sites-available/000-default.conf
COPY SuiteCRM-8.10.2.zip /tmp/suitecrm.zip
RUN unzip -q /tmp/suitecrm.zip -d /var/www/html && rm /tmp/suitecrm.zip \
&& cd /var/www/html \
&& find . -type d -not -perm 2755 -exec chmod 2755 {} \; \
&& find . -type f -not -perm 0644 -exec chmod 0644 {} \; \
&& chmod +x bin/console \
&& chown -R www-data:www-data /var/www/htmlФайл suitecrm.conf направляет веб-сервер в каталог public, как требует руководство по настройке веб-сервера: так наружу не попадают файлы вне этого каталога. Параметр AllowOverride All и модуль mod_rewrite нужны, чтобы работали правила .htaccess, без которых не открываются маршруты вроде api/graphql. Это содержимое файла:
<VirtualHost *:80>
DocumentRoot /var/www/html/public
<Directory /var/www/html/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>В файл php-suitecrm.ini я кладу строку error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE & ~E_WARNING, которую рекомендует документация, и дополнительно поднимаю лимиты памяти и загрузки файлов под ваши вложения; значения подберите по размерам файлов отдела.
Теперь docker-compose.yml. Пароли в нём не пишем: они берутся из файла .env (DB_ROOT_PASSWORD, DB_PASSWORD). Тег образа базы mariadb:11.4, тег собственного образа с номером версии SuiteCRM; latest я не использую нигде. Неконтролируемые обновления по этому тегу ломают базу, и это правило я перенёс из эксплуатации EspoCRM. База стартует с проверкой готовности, и приложение ждёт её. Приложение слушает только 127.0.0.1:8080.
services:
db:
image: mariadb:11.4
restart: unless-stopped
environment:
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MARIADB_DATABASE: suitecrm
MARIADB_USER: suitecrm
MARIADB_PASSWORD: ${DB_PASSWORD}
volumes:
- suitecrm_db:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 10
app:
build: ./app
image: suitecrm-local:8.10.2
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:8080:80"
volumes:
- suitecrm_app:/var/www/html
worker:
image: suitecrm-local:8.10.2
restart: unless-stopped
user: www-data
depends_on:
- app
working_dir: /var/www/html
command: ["php", "bin/console", "messenger:consume", "internal-async", "--time-limit=3600", "--memory-limit=256M"]
volumes:
- suitecrm_app:/var/www/html
volumes:
suitecrm_db:
suitecrm_app:Обратите внимание на том suitecrm_app, который монтируется в /var/www/html. При первом запуске Docker наполняет его содержимым образа, а дальше он живёт отдельно: установщик пишет туда конфигурацию и каталоги upload и cache, и всё это переживёт пересборку контейнера. Обратная сторона: пересборка образа не обновляет SuiteCRM внутри тома, обновление версии делается штатной командой SuiteCRM, о ней в шестом разделе. И резервная копия обязана включать этот том, а не только базу.
Установщик и первые настройки
Соберите и поднимите стек: docker compose up -d --build. Когда база станет здоровой, а приложение запустится, можно запускать установщик. В SuiteCRM 8 два способа: веб-мастер в браузере и консольная команда. Для сервера удобнее консоль, потому что пароли не вводятся в форму через незащищённый канал и процесс воспроизводим. Параметры по документации: -u и -p логин и пароль администратора, -U и -P пользователь и пароль базы, -H хост, -N имя базы, -S адрес сайта, -d демо-данные.
docker compose exec -u www-data app ./bin/console suitecrm:app:install \
-u "admin" -p "$ADMIN_PASSWORD" \
-U suitecrm -P "$DB_PASSWORD" -H db -N suitecrm \
-S "https://crm.example.com/" -d "no"Что я проверяю в этом шаге. Хост базы указываю именем сервиса db, это работает по сети Docker; документация советует IP вместо localhost, чтобы не попасть на проблемы с сокетом, и имя сервиса решает ту же задачу. Адрес сайта -S вписываю итоговый, с https и доменом, потому что SuiteCRM по умолчанию считает адрес из site_url доверенным хостом, а localhost добавляет сам. Демо-данные отключаю. Если установщик ругается на то, что база уже существует, я создаю базу без MARIADB_DATABASE или выдаю пользователю нужные права: поведение этого шага проверьте на тестовом сервере, я описываю его по документации.
Веб-мастер остаётся запасным вариантом: его адрес открывается в браузере, и он проходит проверку системы с красными флажками. Эти флажки полезно увидеть даже тем, кто ставит командой: если в списке не хватает расширения PHP, исправляйте Dockerfile, а не игнорируйте предупреждение.
После установки сделайте минимум для пилота. Смените пароль администратора, если он где-то светился. Создайте роли и пользователей под отдел. Настройте воронку и обязательные поля, но не больше десятка: чем больше обязательных полей, тем чаще менеджеры возвращаются в Excel. Подключите почту. И загрузите тестовый импорт 20–30 записей в порядке справочники, компании, контакты, лиды, чтобы проверить связи до загрузки реальной базы. Интеграции с телефонией и 1С выделите в отдельный этап, о них у меня есть материал про интеграции SuiteCRM.
- Пароль администратора сменён, роли созданы.
- Воронка и обязательные поля заданы, не более десятка.
- Почта подключена и протестирована.
- Тестовый импорт 20–30 записей прогнан.
- Интеграции вынесены в отдельный этап.
HTTPS и обратный прокси
Для публикации CRM нужен публичный домен и корректно настроенный обратный прокси. Я обычно беру Caddy: он сам получает и продлевает сертификаты, если домен указывает на сервер, порты 80 и 443 открыты наружу и каталог данных записываемый и сохраняется. По документации Caddy использует Let's Encrypt или ZeroSSL и сам перенаправляет HTTP на HTTPS. Конфигурация из двух строк в файле Caddyfile:
crm.example.com {
reverse_proxy 127.0.0.1:8080
}Если Caddy запущен контейнером, а не на хосте, адрес бэкенда указывайте по сети Docker и держите каталог данных Caddy на отдельном томе, иначе сертификат будет запрашиваться заново при каждом пересоздании и вы упрётесь в лимиты центра сертификации. Caddy передаёт серверу заголовки X-Forwarded-For, X-Forwarded-Proto и X-Forwarded-Host, а входящие заголовки такого вида по умолчанию отбрасывает, чтобы их нельзя было подделать.
Типовая неприятность за прокси с TLS: приложение «не знает», что снаружи https, и показывает смешанное содержимое или бесконечно перенаправляет после входа. В руководстве SuiteCRM, которое я просмотрел, настройки доверенных прокси не описаны, поэтому на тестовом стенде проверьте вход, выход и открытие вложений через HTTPS. Если используете Traefik или Nginx вместо Caddy, помните общую грабли Docker-стеков: хэш пароля в .env с символами доллара нужно экранировать удвоением, иначе он портится.
Что ещё закрыть на этом шаге. Порт 8080 слушает только локальный адрес, и это стоит проверить снаружи, а не по конфигурации. Доступ к административным разделам ограничьте по возможности списком адресов или VPN. И включите двухфакторную аутентификацию для администраторов, если версия её поддерживает; наличие смотрите в документации вашей версии.
Cron, бэкап и обновление: чтобы сервер дожил до второго квартала
Планировщик. SuiteCRM требует задание cron: без него не работают сценарии, проверка почты и отчёты по расписанию. По документации команду запускают каждую минуту, от пользователя веб-сервера. В Docker-варианте строку ставлю в cron хоста:
* * * * * cd /opt/suitecrm && docker compose exec -T -u www-data app php bin/console schedulers:run > /dev/null 2>&1Там же мониторю, что задача действительно выполняется: симптом пропавшего cron это молчащая почта и «зависшие» запланированные действия. Воркер фоновых задач у нас запущен как отдельный сервис compose, и перезапускается политикой unless-stopped после каждого часа работы по лимиту времени.
Бэкап состоит из двух частей, и обе обязательны: дамп базы и архив тома с файлами приложения (каталог /var/www/html, где лежат загрузки и конфигурация). Пример команд, имена тома и каталога подставьте свои, а имя тома посмотрите командой docker volume ls:
docker compose exec -T db sh -c 'mariadb-dump -u root -p"$MARIADB_ROOT_PASSWORD" --single-transaction suitecrm' | gzip > backup/db-$(date +%F).sql.gz
docker run --rm -v suitecrm_suitecrm_app:/data -v "$PWD/backup":/backup alpine tar czf /backup/app-$(date +%F).tar.gz -C /data .Копии должны уходить за пределы этого сервера: в другое хранилище или на другую машину. Копия, которая лежит рядом с оригиналом, не переживёт ни взлома, ни потери диска.
Проверка восстановления обязательна, иначе копия не доказана. Порядок: поднимите второй, тестовый сервер, восстановите базу и том, войдите в систему, сделайте копию, удалите тестовую запись, восстановите и убедитесь, что запись вернулась. Это занимает час, но превращает «у нас есть бэкап» в факт. Делайте проверку после установки, потом раз в квартал и после каждого крупного обновления. Подробный регламент эксплуатации я описал в статье про эксплуатацию SuiteCRM.
Обновление. Версию меняют не заменой тега образа вслепую, а штатной процедурой SuiteCRM: скачиваете пакет нужной версии, кладёте в каталог tmp/package/upgrade внутри приложения, сбрасываете права, запускаете ./bin/console suitecrm:app:upgrade -t "SuiteCRM-8.x.y", снова сбрасываете права, затем suitecrm:app:upgrade-finalize с тем же параметром и ещё раз права; после этого проверяете разделы миграций в администрировании. Обновление между версиями 8.x делается пакетом той версии, до которой вы обновляетесь. Сначала проходите процедуру на копии, потом на боевом сервере и обязательно после свежего бэкапа; тестовый контур описан в материале про тестовую среду для обновлений.
«Красная Нить»: сервер для рекламного агентства на 17 рабочих мест
Условный пример: рекламное агентство «Красная Нить», 17 рабочих мест, из них пять менеджеров по работе с клиентами, четыре проектных менеджера, пять креаторов и дизайнеров, остальные руководство и бухгалтерия. В CRM нужны клиенты, сделки с длинным циклом, коммерческие предложения и контроль задач по каналам привлечения. Цифры придуманы для иллюстрации. Системный администратор в штате один, и CRM он берёт на себя вместе с остальными задачами.
План вечера. Сначала VPS и Docker, около двадцати минут, если всё идёт без сюрпризов. Затем сборка образа и подъём стека, ещё полчаса вместе с ожиданием загрузки слоёв. Установщик и проверка расширений PHP занимают минут пятнадцать. HTTPS через прокси ещё около четверти часа, если DNS уже настроен. Остаётся настройка ролей, воронки и тестовый импорт, и на это нужен последний час. Итого около двух с половиной часов. Это оценка для проверки, она зависит от опыта и скорости канала, и я называю её только как ориентир.
Где агентство, скорее всего, споткнётся. Первое: образ соберётся, а установщик не примет конфигурацию, потому что не хватает расширения; поможет сверка с php -m. Второе: прокси переведёт на https, а приложение продолжит ссылаться на http; проверка входа сразу покажет это. Третье: через две недели обнаружится, что cron не запускался, потому что строку внесли не в тот crontab. Для каждой проблемы у меня есть диагностическая команда, и они все в этой статье.
Следующий шаг после запуска: пилот на реальных сделках. Три менеджера ведут по одной сделке в системе, администратор фиксирует остановки, по итогам двух недель принимается решение, нужны ли доработки или смена системы. Готовность сервера к пилоту я проверяю по короткому списку из восьми пунктов: HTTPS работает, пароль администратора сменён, теги образов зафиксированы, cron-задачи выполняются, бэкап базы и тома создан, восстановление проверено на тестовом сервере, обновления идут по регламенту, доступ ограничен по ролям.
Самый частый пропуск в этом списке — проверка восстановления. Его откладывают, потому что система «и так работает». Я не запускаю отдел на сервере, пока восстановление не проверено, потому что потеря клиентской базы стоит дороже любой экономии на вечере.
Частые вопросы
Как установить SuiteCRM на Ubuntu?
Поднимите VPS, установите Docker и docker compose, соберите образ на php:8.3-apache с расширениями PHP, поднимите стек с MariaDB 11.4, запустите установщик (консольная команда suitecrm:app:install или веб-мастер) и настройте HTTPS через прокси. Версию и требования берите из документации SuiteCRM.
Какие требования у SuiteCRM к серверу?
Для SuiteCRM 8.10 документация называет PHP 8.2, 8.3 или 8.4, MariaDB 10.6–11.8 или MySQL 8.0 и 8.4 и Apache 2.4. Цену VPS сравнивайте у хостеров по памяти, диску и каналу на день заказа.
Можно ли использовать тег latest для образа SuiteCRM?
Нет, для production фиксируйте версию образа. Неконтролируемые обновления через latest ломают базу, это правило из эксплуатации EspoCRM, применимое к любому CRM-стеку. Версия SuiteCRM в нашей сборке записана в теге образа и в самом пакете.
Как сделать бэкап SuiteCRM в Docker?
Нужны две вещи: дамп базы (mariadb-dump) и архив тома с файлами приложения (/var/www/html: загрузки и конфигурация). Копии храните вне сервера и проверяйте восстановление на тестовой машине, иначе копия не доказана.
Как обновлять SuiteCRM после установки?
Штатно, командами suitecrm:app:upgrade и suitecrm:app:upgrade-finalize с пакетом нужной версии, на копии, а затем на боевом сервере после бэкапа. Заменой тега образа обновление не делается, потому что файлы приложения живут в томе.
Что делать после установки SuiteCRM, чтобы отдел начал работать?
Создать роли и пользователей, настроить воронку и минимум обязательных полей, подключить почту и загрузить тестовый импорт 20–30 записей. Интеграции с телефонией и 1С выделите в отдельный этап после первого месяца работы.
Источники
- SuiteCRM 8, матрица совместимости — PHP 8.2–8.4, MariaDB 10.6/10.11/11.4/11.8, MySQL 8.0 и 8.4, Apache 2.4; с версии 8.7 PHP 7.4 не поддерживается. https://docs.suitecrm.com/8.x/admin/compatibility-matrix/
- SuiteCRM 8, руководство по веб-серверу и установщику — DocumentRoot на public, AllowOverride All, mod_rewrite, список расширений PHP, команда suitecrm:app:install, права на файлы. https://docs.suitecrm.com/8.x/admin/installation-guide/webserver-setup-guide/ и https://docs.suitecrm.com/8.x/admin/installation-guide/running-the-cli-installer/
- SuiteCRM 8, планировщик, Messenger и обновление — Cron ./bin/console schedulers:run каждую минуту от пользователя веб-сервера; Messenger worker messenger:consume internal-async --time-limit=3600 --memory-limit=256M (8.10); пакет обновления в tmp/package/upgrade, команды suitecrm:app:upgrade и upgrade-finalize с -t. https://docs.suitecrm.com/8.x/admin/administration-panel/schedulers/ , https://docs.suitecrm.com/8.x/admin/async-tasks/messenger-setup/ и https://docs.suitecrm.com/8.x/admin/upgrading/running-the-upgrade/
- Docker Engine на Ubuntu и официальные образы php и mariadb — Установка из репозитория Docker, docker-php-ext-install, a2enmod rewrite, переменные MARIADB_*, healthcheck.sh. https://docs.docker.com/engine/install/ubuntu/ и https://hub.docker.com/_/php
- Caddy, автоматический HTTPS и reverse_proxy — Условия выдачи сертификатов, заголовки X-Forwarded-*. https://caddyserver.com/docs/automatic-https
