Discourse на сервере: частые ошибки и решения
Discourse — не «ещё один PHP-форум», а Rails-приложение с Postgres, Redis и Sidekiq внутри, упакованное в один Docker-контейнер. Отсюда специфика поломок: часть проблем классические (SSL, реверс-прокси), часть — чисто дискурсовские (rebuild съедает память, без SMTP форум не работает). Ниже — что чаще всего ломается после установки и как это чинить без переустановки с нуля.
Содержание
- Как устроен Discourse на сервере и почему это важно понимать
- Rebuild падает по памяти: Killed на сборке ассетов
- Email не отправляется: без рабочего SMTP форум почти бесполезен
- SSL-сертификат не выпускается через launcher enable-ssl
- Sidekiq: очередь фоновых задач растёт, письма и уведомления зависают
- Свой nginx перед Discourse: реверс-прокси и почему нельзя просто «прописать location»
- Бэкапы и восстановление Discourse
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Как устроен Discourse на сервере и почему это важно понимать
Официальный и единственный поддерживаемый способ развернуть Discourse — через discourse_docker. Внутри одного контейнера app runit поднимает сразу несколько процессов: Postgres, Redis, Unicorn (сам Rails), Sidekiq (очередь фоновых задач) и nginx, слушающий 80/443 на хосте. Конфигурация — единственный файл containers/app.yml, а не набор .env-переменных по сервисам.
Установка:
apt update && apt install -y docker.io git
git clone https://github.com/discourse/discourse_docker.git /var/discourse
cd /var/discourse
./discourse-setup
Мастер discourse-setup задаёт вопросы (домен, email админа, SMTP) и сам генерирует containers/app.yml. Ключевой момент: rebuild — не перезапуск, а полное пересоздание образа приложения из шаблонов. Он уничтожает контейнер и собирает новый, но данные (Postgres, Redis, аплоады) переживают rebuild, потому что живут в bind-mount /var/discourse/shared/standalone на хосте, а не внутри контейнера. Отсюда правило: любые правки внутри работающего контейнера без записи в app.yml — временные, следующий rebuild их сотрёт. Все изменения вносите в app.yml и применяйте через:
cd /var/discourse
./launcher rebuild app
Это понимание снимает добрую половину последующих вопросов «почему настройки не применились».
Rebuild падает по памяти: Killed на сборке ассетов
Самая частая жалоба новичков: ./launcher rebuild app долго идёт, а на шаге компиляции ассетов (assets:precompile) процесс обрывается со словом Killed, без внятной ошибки Ruby.
Причина почти всегда одна — OOM killer ядра прибил процесс из-за нехватки памяти. Проверить:
dmesg | grep -i -E 'killed process|out of memory'
Если видите там ruby или sprockets — это оно. Discourse формально можно поставить на минимальную конфигурацию, но сборка ассетов требовательна к RAM: на 1–2 ГБ без свопа rebuild падает почти гарантированно, комфортно процесс проходит от 4 ГБ. Точные цифры зависят от версии и набора плагинов — ориентируйтесь на запас, а не на минимум из документации.
Варианты решения:
- Добавить swap, если апгрейд тарифа не вариант прямо сейчас:
fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
Подробнее про выбор размера свопа и когда он реально спасает — в статье про своп и производительность VPS.
- Снизить параллелизм сборки — в
containers/app.yml, в блокеenv, добавить:
env:
DISCOURSE_PRECOMPILE_JOBS: 1
UNICORN_WORKERS: 2
Сборка станет дольше, но не будет пытаться съесть всю память разом.
- Увеличить память сервера — если форум живой (десятки-сотни пользователей в день), это самое надёжное решение: своп на медленном диске сильно тормозит саму сборку, а не только спасает от падения.
После правки app.yml — снова ./launcher rebuild app, свежий своп подключать не нужно перезапускать сервер отдельно, swapon уже активен.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверEmail не отправляется: без рабочего SMTP форум почти бесполезен
Discourse жёстко завязан на исходящую почту: активация аккаунта, приглашения, сброс пароля, email-дайджесты — всё это письма. Без настроенного SMTP новые пользователи физически не смогут завершить регистрацию.
SMTP задаётся в containers/app.yml, блок env:
env:
DISCOURSE_SMTP_ADDRESS: smtp.example.com
DISCOURSE_SMTP_PORT: 587
DISCOURSE_SMTP_USER_NAME: postmaster@example.com
DISCOURSE_SMTP_PASSWORD: "пароль_или_apikey"
DISCOURSE_SMTP_ENABLE_START_TLS: true
DISCOURSE_NOTIFICATION_EMAIL: noreply@example.com
Важно: переменные env применяются только через ./launcher rebuild app, обычного restart контейнера недостаточно — старая конфигурация Rails уже запечена в процесс.
Проверить отправку без ожидания реального пользователя: зайдите в Admin → Email → Settings и нажмите «Send Test Email» — Discourse честно покажет ошибку SMTP-сервера, если она есть (неверный логин, TLS, порт).
Отдельная, более коварная проблема — SMTP настроен, тестовое письмо ушло, но письма пользователям падают в спам или отклоняются принимающим сервером. Обычно это не баг Discourse, а отсутствующие или неверные SPF/DKIM/DMARC-записи у домена отправки. Логика диагностики та же, что для любого self-hosted почтового стека — она разобрана в статье про попадание писем в спам: проверьте SPF-запись домена, включите DKIM у SMTP-провайдера, убедитесь, что DISCOURSE_NOTIFICATION_EMAIL совпадает с доменом этих записей.
SSL-сертификат не выпускается через launcher enable-ssl
Discourse умеет сам получать сертификат Let's Encrypt без стороннего nginx или certbot на хосте — через ./discourse-setup при первой установке или вручную:
cd /var/discourse
./launcher enable-ssl app
Команда подключает шаблоны templates/web.ssl.template.yml и templates/web.letsencrypt.ssl.template.yml в app.yml и пересобирает контейнер. Падает по трём типовым причинам:
- A-запись домена ещё не указывает на сервер. Проверьте до запуска:
dig +short forum.example.com
Значение должно совпадать с IP сервера. DNS может распространяться до суток — не запускайте enable-ssl сразу после смены записи.
- Порт 80 занят или закрыт файрволом. Let's Encrypt проверяет владение доменом HTTP-запросом на 80-й порт — если он недоступен снаружи или его держит другой веб-сервер на хосте, выпуск не пройдёт:
ufw status
curl -I http://forum.example.com/
- Упёрлись в лимит Let's Encrypt (5 неудачных попыток на домен в неделю) после серии проваленных запусков по первым двум причинам — остаётся только ждать окна лимита, форсировать нечем.
Автопродление сертификата работает изнутри контейнера через cron, отдельно настраивать не нужно. Проверить срок действия:
echo | openssl s_client -connect forum.example.com:443 2>/dev/null | openssl x509 -noout -dates
Общие нюансы работы с Let's Encrypt вне контекста Discourse — в статье про частые ошибки Let's Encrypt SSL.
Sidekiq: очередь фоновых задач растёт, письма и уведомления зависают
Sidekiq в Discourse отвечает почти за всё асинхронное: отправку писем, обработку загруженных изображений, дайджесты, поиск дубликатов — если он встал, форум внешне работает, но письма не уходят и посты «зависают» в обработке.
Проверить состояние можно в браузере под админом на https://forum.example.com/sidekiq — там видно глубину очередей и упавшие задачи. Если панель недоступна или очередь только растёт, зайдите внутрь контейнера:
cd /var/discourse
./launcher enter app
sv status sidekiq
sv — это supervisor runit, которым внутри контейнера управляются все процессы. Если Sidekiq не в статусе run, перезапустите его:
sv restart sidekiq
Частая причина падения Sidekiq — тот же OOM killer, что и при rebuild: если контейнеру не хватает памяти под пиковую нагрузку (много загрузок картинок разом, большой дайджест по всем пользователям), ядро убивает процесс. Та же команда dmesg | grep -i oom работает и здесь, уже на живой системе. Логи для точечной диагностики:
tail -f /var/discourse/shared/standalone/log/rails/production.log
Если очередь стабильно не успевает за нагрузкой (не разовый сбой, а постоянная картина), увеличьте число воркеров в app.yml:
env:
UNICORN_SIDEKIQS: 1
DISCOURSE_SIDEKIQ_WORKERS: 10
и пересоберите (./launcher rebuild app) — но убедитесь, что памяти сервера хватит на возросшую нагрузку, иначе получите ту же OOM-петлю.
Свой nginx перед Discourse: реверс-прокси и почему нельзя просто «прописать location»
По умолчанию Discourse сам держит порты 80 и 443 внутренним nginx — это прописано в app.yml секцией expose: ["80:80", "443:443"]. Проблема возникает, когда на том же сервере нужен ещё один сайт или внешний прокси: два процесса не могут слушать один порт.
Рабочая схема — отдать TLS-терминацию и порты 80/443 внешнему nginx на хосте, а контейнер Discourse увести на локальный порт. В containers/app.yml:
expose:
- "127.0.0.1:8080:80"
и убрать из templates строки про web.ssl.template.yml / web.letsencrypt.ssl.template.yml — сертификатами теперь занимается внешний nginx. После правки — ./launcher rebuild app.
Конфиг внешнего nginx:
server {
listen 443 ssl;
server_name forum.example.com;
ssl_certificate /etc/letsencrypt/live/forum.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/forum.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
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_http_version 1.1;
proxy_read_timeout 90s;
}
}
Два момента, из-за которых эта схема чаще всего ломается:
- Без
X-Forwarded-Proto $schemeDiscourse не понимает, что запрос пришёл по HTTPS, и уходит в петлю редиректов https → http → https. - MessageBus (механизм живых обновлений — новые посты, счётчики без перезагрузки страницы) держит долгие HTTP-соединения long-polling. Слишком короткий
proxy_read_timeoutрвёт их раньше времени, и обновления приходят рывками.
Общие принципы настройки реверс-прокси на nginx и типичные грабли — в статье про nginx как реверс-прокси; если сама проблема в том, что порт контейнера не пробрасывается на хост вообще, начните с разбора ошибок проброса портов Docker.
Бэкапы и восстановление Discourse
Встроенный бэкап — не опция, а то, чем стоит пользоваться с первого дня: Admin → Backups → Backup. Он снимает дамп Postgres и архив загрузок в один .tar.gz, который ложится в:
/var/discourse/shared/standalone/backups/default/
Автоматическую выгрузку в S3 можно включить прямо в app.yml:
env:
DISCOURSE_BACKUP_LOCATION: s3
DISCOURSE_S3_BACKUP_BUCKET: your-bucket-backups
DISCOURSE_S3_ACCESS_KEY_ID: xxx
DISCOURSE_S3_SECRET_ACCESS_KEY: xxx
DISCOURSE_S3_REGION: eu-central-1
Ручной бэкап и восстановление из консоли, без веб-интерфейса:
cd /var/discourse
./launcher enter app
discourse backup
# для восстановления файл должен лежать в backups/default/
discourse restore имя-файла.tar.gz
Данные Discourse физически лежат в bind-mount /var/discourse/shared на хосте, а не в volume внутри образа — общие принципы бэкапа docker-окружений применимы и к этому каталогу, не только к встроенным архивам форума; они разобраны в статье про бэкап docker volume. Проверяйте восстановление раз в несколько месяцев на тестовом сервере — бэкап, который никогда не разворачивали, нельзя считать рабочим.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Сколько реально нужно RAM для Discourse?
Официальный минимум невелик, но на практике rebuild и Sidekiq под нагрузкой комфортно проходят от 4 ГБ. На 2 ГБ форум может годами работать для небольшого сообщества, но rebuild почти наверняка потребует временный своп.
Можно ли держать Discourse и другой сайт на одном сервере?
Да, но не «из коробки» — по умолчанию Discourse занимает 80/443 сам. Нужен внешний nginx как реверс-прокси перед контейнером (см. раздел выше) либо разные IP на одном сервере под разные проекты.
После rebuild сайт отдаёт 502 — что делать?
Дайте контейнеру 1–2 минуты подняться (Postgres и Sidekiq стартуют не мгновенно), затем проверьте ./launcher logs app и dmesg на признаки OOM — 502 сразу после rebuild чаще всего значит, что процесс не успел подняться из-за нехватки памяти.
Обязательно ли платить за плагины?
Нет, большинство официальных плагинов бесплатны и ставятся добавлением git-репозитория в app.yml с последующим rebuild. Платное — это в основном хостинг-сервисы и премиум-темы, не сам движок.
Как обновить Discourse без риска всё сломать?
Через Admin → Upgrade в веб-интерфейсе (обновляет по шагам) либо ./launcher rebuild app после git pull в /var/discourse. Перед крупным апгрейдом версии снимайте свежий бэкап — миграции базы необратимы без него.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →