MAATRIX / Блог / Chatwoot в Docker Compose: готовый файл

Chatwoot в Docker Compose: готовый файл

MAATRIX

Intercom и Zendesk удобны, пока не приходит счёт за подписку на команду из пяти-семи человек — а он приходит быстро, потому что тарифы там считаются "за агента". Chatwoot — open-source альтернатива с тем же набором: виджет чата на сайте, почта, Telegram, WhatsApp и Facebook в одном инбоксе, автоответы, теги, отчёты. Разворачивается через Docker Compose на своём сервере, но состоит не из одного контейнера, а из связки Rails-приложения, фонового воркера Sidekiq, PostgreSQL и Redis — и если пропустить один нюанс, самый частый результат: приложение открывается, а письма и уведомления просто не ходят. Ниже — рабочий compose-файл и список граблей первого запуска.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Зачем Chatwoot четыре контейнера, а не один

Chatwoot — Ruby on Rails приложение из двух ролей одного кода: rails обслуживает веб-интерфейс, API и WebSocket-соединения (через ActionCable в инбокс агента прилетают новые сообщения без обновления страницы), а sidekiq — фоновый воркер, разбирающий очередь задач: отправку email, обработку входящей почты, вебхуки интеграций, автоматизации. Это разные контейнеры с разными командами запуска одного образа.

PostgreSQL хранит переписку, контакты, настройки инбоксов. Redis здесь не кэш "для скорости", а обязательная часть системы: через него Sidekiq берёт задачи из очереди, и без Redis фоновые задачи просто не выполняются — контейнер rails при этом продолжит работать и показывать интерфейс, что и вводит в заблуждение. Отсюда правило: если письма или уведомления агентам не работают, в первую очередь проверяйте не rails, а Sidekiq и его связь с Redis.

Готовый docker-compose.yml

version: "3.8"

services:
  rails:
    image: chatwoot/chatwoot:v3.14.0
    restart: unless-stopped
    entrypoint: docker/entrypoints/rails.sh
    command: ["bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"]
    env_file: .env
    depends_on: [postgres, redis]
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - chatwoot_storage:/app/storage
    networks: [chatwoot_net]

  sidekiq:
    image: chatwoot/chatwoot:v3.14.0
    restart: unless-stopped
    entrypoint: docker/entrypoints/rails.sh
    command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"]
    env_file: .env
    depends_on: [postgres, redis]
    volumes:
      - chatwoot_storage:/app/storage
    networks: [chatwoot_net]

  postgres:
    image: pgvector/pgvector:pg16
    restart: unless-stopped
    environment:
      POSTGRES_USER: chatwoot
      POSTGRES_PASSWORD: "СЛОЖНЫЙ_ПАРОЛЬ"
      POSTGRES_DB: chatwoot
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks: [chatwoot_net]

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass "ПАРОЛЬ_REDIS" --appendonly yes
    volumes:
      - redis_data:/data
    networks: [chatwoot_net]

volumes:
  chatwoot_storage:
  postgres_data:
  redis_data:

networks:
  chatwoot_net:
    driver: bridge

Образ pgvector/pgvector:pg16 — обычный PostgreSQL 16 с предустановленным расширением pgvector для поиска по базе знаний с ИИ-подсказками (Captain AI начиная с 3.x). Если эти функции не нужны, подойдёт и postgres:16 — но с pgvector не придётся мигрировать базу, если решите включить их позже.

Рядом — файл .env:

SECRET_KEY_BASE=сгенерировать_командой_ниже
FRONTEND_URL=https://support.example.com
INSTALLATION_ENV=docker
RAILS_ENV=production
NODE_ENV=production
RAILS_MAX_THREADS=5

POSTGRES_HOST=postgres
POSTGRES_USERNAME=chatwoot
POSTGRES_PASSWORD=СЛОЖНЫЙ_ПАРОЛЬ
POSTGRES_DATABASE=chatwoot

REDIS_URL=redis://:ПАРОЛЬ_REDIS@redis:6379

MAILER_SENDER_EMAIL=Support <support@example.com>
SMTP_ADDRESS=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=support@example.com
SMTP_PASSWORD=пароль_smtp
SMTP_AUTHENTICATION=login
SMTP_ENABLE_STARTTLS_AUTO=true
SMTP_DOMAIN=example.com

DEFAULT_LOCALE=ru

SECRET_KEY_BASE генерируется одной командой, и без неё контейнер rails откажется стартовать:

openssl rand -hex 64

FRONTEND_URL — не косметика: от него зависят ссылки в письмах-приглашениях, origin для виджета и CORS. Домен без https:// или без прокси перед ним сломает установку пароля по ссылке из письма.

Запуск:

mkdir -p ~/chatwoot && cd ~/chatwoot
nano docker-compose.yml   # вставить содержимое выше
nano .env                 # вставить переменные и пароли
docker compose up -d postgres redis
sleep 10
docker compose run --rm rails bundle exec rails db:chatwoot_prepare
docker compose up -d

Команда db:chatwoot_prepare создаёт схему базы, накатывает миграции и заполняет служебные справочники. Без неё rails и sidekiq уйдут в цикл рестарта с ошибками вида relation "users" does not exist.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Обратный прокси, TLS и WebSocket

Chatwoot слушает на 3000 порту обычный HTTP, TLS сам не терминирует, а ActionCable для realtime в инбоксе агента требует проброса WebSocket-заголовков:

server {
    listen 443 ssl http2;
    server_name support.example.com;

    ssl_certificate     /etc/letsencrypt/live/support.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/support.example.com/privkey.pem;
    client_max_body_size 50m;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400;
    }
}

server {
    listen 80;
    server_name support.example.com;
    return 301 https://$host$request_uri;
}

proxy_read_timeout 86400 увеличен намеренно: WebSocket-соединение живёт долго, а дефолтный таймаут nginx рвёт его между сообщениями, из-за чего в интерфейсе агента периодически всплывает "переподключение". Автоматический выпуск сертификатов без ручного certbot — вариант с Traefik, базовая настройка описана в статье Traefik как reverse proxy для Docker; про сам механизм получения сертификата — в статье как установить и настроить Let's Encrypt SSL на VPS.

Первый запуск: администратор, виджет, почта

После docker compose up -d откройте https://support.example.com — Chatwoot покажет форму регистрации первого аккаунта. Она доступна только пока в базе нет суперадминистратора; дальше новых агентов приглашает уже он изнутри интерфейса. Если вместо формы — 502 или белый экран, смотрите логи по каждому контейнеру, проблема может быть и в rails, и в базе:

docker compose logs -f rails
docker compose logs -f postgres

Типичная причина — rails стартовал раньше, чем PostgreSQL принял подключения (depends_on в Compose ждёт только запуска контейнера, не готовности сервиса), контейнер уходит в рестарт-луп с PG::ConnectionBad. Лечится вручную:

docker compose restart rails sidekiq

После создания инбокса типа Website Chatwoot выдаёт сниппет виджета:

<script>
  (function(d,t) {
    var BASE_URL = "https://support.example.com";
    var g = d.createElement(t), s = d.getElementsByTagName(t)[0];
    g.src = BASE_URL + "/packs/js/sdk.js";
    g.defer = true; g.async = true;
    s.parentNode.insertBefore(g, s);
    g.onload = function() {
      window.chatwootSDK.run({ websiteToken: "ТОКЕН_ИНБОКСА", baseUrl: BASE_URL });
    };
  })(document, "script");
</script>

Если виджет не появляется на сайте — либо блокировщик рекламы у тестирующего (классическая цель фильтров), либо BASE_URL в сниппете не совпадает с FRONTEND_URL из .env: домены должны быть идентичны, иначе браузер блокирует запрос по CORS.

Для почты SMTP-блок из .env достаточен для отправки уведомлений и ответов агентов. Если письма не уходят вовсе — проверьте связку сервера с внешним SMTP, типичные причины разобраны в статье Postfix не отправляет почту наружу: причины и решение — набор причин (заблокированный 25 порт у хостера, отсутствующие SPF/DKIM) тот же самый, даже если Postfix у вас не используется. Приём писем проще всего настроить через IMAP-опрос ящика прямо в интерфейсе инбокса — Chatwoot сам проверяет почту и создаёт разговоры из новых писем; приём напрямую через собственный MX и ActionMailbox сложнее и оправдан только при большом объёме входящей почты.

Ресурсы, бэкапы и обновление версии

Под небольшую команду поддержки (до 5-10 агентов) закладывайте минимум 2 vCPU и 4 ГБ RAM — ощутимую долю съедает связка Rails + Sidekiq, а не PostgreSQL, как можно ожидать по аналогии с другими self-hosted сервисами. Если Sidekiq регулярно упирается в очередь (растущий backlog в логах), добавляйте ресурсы в первую очередь под него.

Бэкапить нужно postgres_data (переписка, контакты, настройки — критично) и chatwoot_storage (вложения, если храните их локально, а не на S3-совместимом хранилище):

docker compose exec postgres pg_dump -U chatwoot chatwoot > backups/chatwoot-$(date +%F).sql

Для регулярных автоматических бэкапов Docker-томов разумнее не писать скрипт с нуля, а использовать BorgBackup — установка и подключение к volume разобраны в статье BorgBackup в Docker Compose: готовый файл, а общие грабли именно с бэкапом Docker-томов — в статье бэкап Docker volume на сервере: частые ошибки и решения.

Обновление — смена тега образа сразу у rails и sidekiq (они используют один образ) и ручной прогон миграций. Перед мажорным апгрейдом (v3.xv4.x) обязателен свежий дамп базы:

# image: chatwoot/chatwoot:v3.14.0 -> chatwoot/chatwoot:v3.15.0
docker compose pull rails sidekiq
docker compose stop rails sidekiq
docker compose run --rm rails bundle exec rails db:migrate
docker compose up -d rails sidekiq

В отличие от некоторых self-hosted сервисов, entrypoint Chatwoot не накатывает миграции автоматически при каждом старте — это осознанное решение авторов: миграции на проде должны быть управляемым шагом, а не побочным эффектом рестарта контейнера.

Частые проблемы при развёртывании

СимптомВероятная причинаЧто проверить
Письма и уведомления не отправляются, интерфейс работаетSidekiq не поднялся или не видит Redisdocker compose logs sidekiq, пароль в REDIS_URL
rails в рестарт-луп сразу после стартаПустой SECRET_KEY_BASEзначение переменной в .env
PG::ConnectionBad при стартеrails/sidekiq стартовали раньше PostgreSQLdocker compose restart rails sidekiq через 10-15 сек
Постоянное "переподключение" в инбоксе агентаПрокси рвёт WebSocket по таймаутуproxy_read_timeout и заголовки Upgrade/Connection
Виджет не загружается на сайтеBASE_URL не совпадает с FRONTEND_URL, или блокировщик рекламывкладка Network в консоли браузера
relation does not exist при первом запускеПропущен rails db:chatwoot_prepareвыполнить команду вручную после старта postgres

Общие принципы работы с Docker Compose в проде — порядок запуска сервисов, restart policy, health check — разобраны в статье Docker Compose для продакшена: пошаговая установка. Если проблема на стороне базы, а таблица выше не помогла — смотрите PostgreSQL на сервере: частые ошибки и решения.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли обойтись без Redis?

Нет. Через него работает очередь Sidekiq (email, вебхуки, автоматизации) и часть realtime-механики ActionCable. Без него приложение стартует, но фоновые задачи не выполняются — маскируется под "всё работает, но письма не уходят".

Сколько ресурсов нужно на команду из 10 агентов?

Ориентировочно от 2 vCPU и 4 ГБ RAM хватает для старта, но точная цифра зависит от числа одновременных диалогов и того, включены ли ИИ-функции Captain. Проверяйте по факту через docker stats и очередь Sidekiq.

Нужен ли pgvector, если ИИ-функции не нужны?

Нет, обычный postgres:16 работает без ограничений для стандартного функционала. Ставить pgvector сразу имеет смысл, только если планируете включить поиск по базе знаний с ИИ-подсказками позже — не придётся мигрировать базу.

Как перенести Chatwoot на другой сервер?

Снять pg_dump и скопировать том chatwoot_storage, поднять идентичный compose-стек с теми же SECRET_KEY_BASE и переменными окружения, восстановить базу через psql, распаковать storage в volume. DNS переключать только после проверки, что интерфейс и виджет работают.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →