Chatwoot на VPS в Docker: единый инбокс для чатов, почты и мессенджеров

Если клиенты пишут вам одновременно в чат на сайте, на почту, в Telegram и во «ВКонтакте», рано или поздно кто-то останется без ответа: вкладки перепутаются, история потеряется, а менеджер будет вручную переносить контекст. Chatwoot решает это единым инбоксом, куда стекаются все каналы, с ответственными, метками и историей по каждому клиенту. Это open-source платформа поддержки на Ruby on Rails — self-hosted аналог Intercom и Zendesk Chat, только без платы за каждого агента и без чужого сервера под перепиской.

Данные клиентов остаются у вас (для многих проектов это требование по персональным данным), число агентов не ограничено лицензией, а интеграции — свой SMTP, Redis и вебхуки — вписываются в существующую инфраструктуру. Обратная сторона честная: обновления, бэкапы и мониторинг становятся вашей рутиной. Ниже — как развернуть Chatwoot на Ubuntu 24.04 в Docker без ручной сборки Ruby-окружения. Актуальные конфигурации серверов с ценами — в каталоге VPS.

Что понадобится

Chatwoot — не одно лёгкое приложение, а связка из четырёх сервисов: Rails, фоновый воркер Sidekiq, PostgreSQL и Redis. Память здесь расходуется быстрее, чем у обычного сайта. Ориентиры по конфигурации:

СценарийCPURAMДиск
Тест, 1–2 агента2 vCPU4 GB40 GB SSD
Рабочий инстанс, 5–10 агентов4 vCPU8 GB60 GB SSD
Активная поддержка, много каналов4–8 vCPU16 GB80+ GB SSD

Для старта хватает 4 ГБ, но 8 ГБ дают запас на пиках Sidekiq; на VPS с 1–2 ГБ стек почти гарантированно убьёт OOM. Кроме сервера нужны Ubuntu 24.04 с root-доступом, домен для панели, внешний SMTP-релей и открытые 80/443.

Готовим систему и заводим отдельного пользователя: под root прод-сервис держать не стоит.

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git ca-certificates gnupg ufw
sudo adduser deploy
sudo usermod -aG sudo deploy
su - deploy

Установка пошагово

Docker ставим из официального репозитория, а не из дистрибутивных пакетов — так вы получите актуальный compose-плагин:

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo tee /etc/apt/keyrings/docker.asc > /dev/null
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
docker compose version
sudo usermod -aG docker deploy

После добавления в группу docker перелогиньтесь, иначе каждую команду придётся запускать через sudo. Дальше — официальный репозиторий Chatwoot с готовым production-файлом:

git clone https://github.com/chatwoot/chatwoot.git
cd chatwoot
cp .env.example .env
openssl rand -hex 64

Последняя команда выдаёт секрет для SECRET_KEY_BASE — впишите его в .env один раз и больше не меняйте, иначе сессии будут слетать при каждом пересоздании контейнера. В том же файле задайте FRONTEND_URL=https://support.example.com, POSTGRES_HOST=postgres, POSTGRES_USERNAME, POSTGRES_PASSWORD, POSTGRES_DATABASE, REDIS_URL и SMTP-параметры (SMTP_ADDRESS, SMTP_PORT=587, SMTP_USERNAME, SMTP_PASSWORD, SMTP_ENABLE_STARTTLS_AUTO=true).

Поднимаем стек и один раз инициализируем базу:

docker compose -f docker-compose.production.yaml up -d
docker compose -f docker-compose.production.yaml ps
docker compose -f docker-compose.production.yaml run --rm rails bundle exec rails db:chatwoot_prepare
docker compose -f docker-compose.production.yaml logs -f rails

db:chatwoot_prepare прогоняет миграции и создаёт справочники. Это шаг первого деплоя: без него rails и sidekiq уходят в цикл рестарта с ошибкой relation "users" does not exist. Если rails падает сразу после старта, в 90% случаев виновата строка подключения к PostgreSQL или пустой SECRET_KEY_BASE.

Rails слушает порт 3000 и наружу смотреть не должен. Ставим Nginx и закрываем его TLS через Certbot:

sudo apt install -y nginx
sudo ln -s /etc/nginx/sites-available/chatwoot /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d support.example.com

В конфиге домена нужен прокси на http://127.0.0.1:3000 с заголовками Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto и пробросом WebSocket: proxy_http_version 1.1, Upgrade $http_upgrade, Connection "upgrade", proxy_read_timeout 86400. Без последней группы интерфейс откроется, но новые сообщения перестанут приходить без перезагрузки страницы. Файрвол оставляем минимальным:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
ss -tlnp | grep 3000

В выводе последней команды адрес должен быть 127.0.0.1:3000, а не 0.0.0.0:3000: после правок compose-файла это стоит перепроверять. Открываем https://support.example.com — Chatwoot покажет форму регистрации первого аккаунта; она доступна, только пока в базе нет суперадминистратора. Дальше в Settings → Inboxes → Add Inbox выбираете канал (Website, Email, API, мессенджер), забираете JS-сниппет виджета и вставляете его перед </body> на сайте. Агентов приглашаете через Settings → Agents — письма уйдут через SMTP из .env. Если писем нет, проверьте SMTP и не заблокирован ли исходящий 587/465 у провайдера.

Docker Compose

Если не хочется тянуть репозиторий, достаточно четырёх сервисов и одного файла. Важное отличие от наивной установки — образ PostgreSQL с предустановленным pgvector: Chatwoot использует расширения pg_trgm и pgvector, и на обычном postgres вы поймаете extension "vector" is not available.

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

Рядом — .env. Здесь важен REDIS_URL с паролем: если Redis запущен с --requirepass, а строка подключения без пароля, Sidekiq молча не сможет забрать задачи:

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

Запуск идёт в два этапа: сначала база, потом подготовка схемы, потом всё остальное. depends_on в Compose ждёт только старта контейнера, а не готовности PostgreSQL принимать подключения, поэтому небольшая пауза здесь не лишняя:

mkdir -p ~/chatwoot && cd ~/chatwoot
nano docker-compose.yml
nano .env
openssl rand -hex 64
docker compose up -d postgres redis
sleep 10
docker compose run --rm rails bundle exec rails db:chatwoot_prepare
docker compose up -d

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

Частые ошибки и решения

Rails перезапускается по кругу. Смотрите логи приложения: docker compose logs -f rails. Три типовые причины — нехватка памяти (проверьте dmesg | grep -i "out of memory" и free -h), пустой SECRET_KEY_BASE и гонка с базой. Для третьего случая помогает healthcheck в depends_on с условием service_healthy либо ручной docker compose restart rails sidekiq через 10–15 секунд после старта базы.

PG::UndefinedTable: relation ... does not exist. На первом запуске нужен db:chatwoot_prepare. При обновлении версии — наоборот, только db:migrate: prepare рассчитан на чистую базу и повторно его гонять не надо.

Письма и уведомления не уходят, хотя интерфейс работает. Почти всегда это Sidekiq и его связь с Redis, а не Rails. Проверьте, что сервис sidekiq есть в compose, и посмотрите docker compose logs -f sidekiq. Redis::CannotConnectError — неверный хост, порт или пароль в REDIS_URL. Если очередь default растёт, а mailers пустая, проблема в SMTP: проверьте SMTP_DOMAIN и блокировку портов у хостера. Быстрый тест отправки:

docker compose exec rails bundle exec rails runner "ActionMailer::Base.mail(from: 'support@example.com', to: 'you@example.com', subject: 'test', body: 'test').deliver_now"

Nginx отдаёт 502/504 или в инбоксе постоянно «переподключение». Это оборванный WebSocket. Проверьте Upgrade/Connection и proxy_read_timeout 86400: дефолтные 60 секунд рвут соединение ActionCable.

Виджет не появляется на сайте. Сравните BASE_URL в сниппете с FRONTEND_URL — домены должны совпадать символ в символ, иначе браузер заблокирует запрос по CORS. Вторая — строгий CSP на сайте, третья — блокировщик рекламы. После правки .env контейнер нужно пересоздать: docker compose up -d --force-recreate rails.

Со временем кончается диск или память. Растут логи Docker и retry-очередь Sidekiq из-за падающих задач. Смотрите docker system df, чистите docker system prune -a --volumes (осторожно: удаляет неиспользуемые тома!) и после исправления первопричины сбрасывайте retry-set: Sidekiq::RetrySet.new.clear. Для Redis на небольшом инстансе задайте maxmemory 512mb и maxmemory-policy allkeys-lru.

Бэкап и обновление

Бэкапить нужно два места: базу (переписка, контакты, настройки) и том chatwoot_storage (вложения, если они хранятся локально, а не в S3). Дамп базы снимается одной командой:

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

Дампы нельзя держать на том же сервере — копируйте их на другое хранилище: диск VPS не спасает ни от ошибки человека, ни от сбоя провайдера. Обновление — смена тега образа у rails и sidekiq плюс ручной прогон миграций; entrypoint Chatwoot миграции сам не накатывает, и это осознанное решение авторов:

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 поддержку предсказуемой: завести инбоксы, вставить сниппет виджета, пригласить агентов и проверить доставку писем. Дальше — следить за очередью Sidekiq, раз в квартал обновлять образ с дампом базы и держать копии за пределами сервера. По нагрузке ориентируйтесь на docker stats: если узкое место — Sidekiq, добавляйте ресурсы под него, а PostgreSQL выносите отдельно только при заметном росте тикетов.

Если сервера ещё нет, начните с 4 vCPU / 8 ГБ — этого хватит инстансу на 5–10 агентов, а под тест сгодится 2 vCPU / 4 ГБ. Подобрать вариант по цене и локации можно в каталоге VPS, а конфигурации под контейнерные сервисы собраны в подборке Docker VPS.