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. Память здесь расходуется быстрее, чем у обычного сайта. Ориентиры по конфигурации:
| Сценарий | CPU | RAM | Диск |
|---|---|---|---|
| Тест, 1–2 агента | 2 vCPU | 4 GB | 40 GB SSD |
| Рабочий инстанс, 5–10 агентов | 4 vCPU | 8 GB | 60 GB SSD |
| Активная поддержка, много каналов | 4–8 vCPU | 16 GB | 80+ 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.