Вы можете разместить несколько независимых установок Discourse на одном сервере (отдельные контейнеры / отдельные порты / отдельные app.yml), не используя функцию «multisite» (мульти-сайт) в Discourse.
Это более ручной процесс по сравнению с multisite, но он обеспечивает изоляцию экземпляров и упрощает последующую миграцию отдельного сайта на собственный сервер.
Один из практических подходов:
• внешний PostgreSQL (один экземпляр)
• внешний Redis (один экземпляр)
• несколько веб-контейнеров Discourse
• один узел Sidekiq
• обратный прокси с проверками работоспособности
Это позволяет полностью избежать использования multisite, сохраняя при этом возможность экономии средств на конфигурациях с низкой нагрузкой.
РУКОВОДСТВО ПО ЗАПУСКУ DISCOURSE В НЕСКОЛЬКИХ КОНТЕЙНЕРАХ
Внешний PostgreSQL + Redis + HAProxy + app1 / app2
- ПАКЕТЫ ХОСТА
| Шаг | Команда |
|---|---|
| Обновление системы | apt-get update |
| Установка базовых инструментов | apt-get install -y ca-certificates curl gnupg lsb-release |
| Установка HAProxy + certbot + socat | apt-get install -y haproxy certbot socat |
- DOCKER СЕТЕВОЕ ПОДКЛЮЧЕНИЕ (ОБЯЗАТЕЛЬНО)
Требуется пользовательская сеть Docker, чтобы контейнеры могли разрешать имена друг друга.
| Шаг | Команда |
|---|---|
| Создание сети | docker network create discourse-net |
| Проверка | docker network ls | grep discourse-net |
Это позволяет корректно работать следующим переменным:
• DISCOURSE_DB_HOST=pg
• DISCOURSE_REDIS_HOST=redis
- СЕКРЕТЫ
| Назначение | Команда |
|---|---|
| Суперпользователь PostgreSQL | export PG_SUPERPASS='REPLACE_ME_super_strong' |
| Пароль базы данных Discourse | export DISCOURSE_DBPASS='REPLACE_ME_discordb_strong' |
| Пароль Redis | export REDIS_PASS='REPLACE_ME_redis_strong' |
| Базовый секретный ключ | export SECRET_KEY_BASE="$(openssl rand -hex 64)" |
- КОНТЕЙНЕР POSTGRES
| Шаг | Команда |
|---|---|
| Создание директории | mkdir -p /var/discourse/external/postgres |
| Запуск контейнера | docker run -d --name pg --restart=always --network=discourse-net -e POSTGRES_PASSWORD="$PG_SUPERPASS" -v /var/discourse/external/postgres:/var/lib/postgresql/data postgres:15 |
| Проверка | docker ps | grep pg |
- СОЗДАНИЕ БАЗЫ ДАННЫХ
| Шаг | Команда |
|---|---|
| Создание роли | docker exec -it pg psql -U postgres -c "CREATE ROLE discourse LOGIN PASSWORD '$DISCOURSE_DBPASS';" |
| Создание БД | docker exec -it pg psql -U postgres -c "CREATE DATABASE discourse OWNER discourse ENCODING 'UTF8' TEMPLATE template0;" |
| Поиск текста | docker exec -it pg psql -U postgres -d discourse -c "ALTER DATABASE discourse SET default_text_search_config = 'pg_catalog.english';" |
| Тест входа | docker exec -it pg psql -U discourse -d discourse -c "select 1;" |
- РАСШИРЕНИЕ PGVECTOR
Требуется для современных версий Discourse.
| Шаг | Команда |
|---|---|
| Установка | docker exec -it pg bash -lc 'apt-get update && apt-get install -y postgresql-15-pgvector && rm -rf /var/lib/apt/lists/*' |
| Создание расширения | docker exec -it pg psql -U postgres -d discourse -c "CREATE EXTENSION IF NOT EXISTS vector;" |
| Проверка | docker exec -it pg psql -U postgres -d discourse -c "SELECT extname FROM pg_extension WHERE extname='vector';" |
- КОНТЕЙНЕР REDIS
| Шаг | Команда |
|---|---|
| Создание директории | mkdir -p /var/discourse/external/redis |
Шаблон конфигурации Redis:
requirepass REPLACE_ME_REDIS
appendonly yes
save 900 1
save 300 10
save 60 10000
| Шаг | Команда |
|---|---|
| Запись конфигурации | tee /var/discourse/external/redis/redis.conf >/dev/null <<EOF |
| Вставка пароля | sed -i "s/REPLACE_ME_REDIS/$REDIS_PASS/" /var/discourse/external/redis/redis.conf |
| Запуск Redis | docker run -d --name redis --restart=always --network=discourse-net -v /var/discourse/external/redis:/data -v /var/discourse/external/redis/redis.conf:/usr/local/etc/redis/redis.conf redis:7-alpine redis-server /usr/local/etc/redis/redis.conf |
| Тест аутентификации | docker exec -it redis redis-cli -a "$REDIS_PASS" ping |
- СТРУКТУРА ДИРЕКТОРИЙ DISCOURSE
| Шаг | Команда |
|---|---|
| Создание базовой директории | mkdir -p /var/discourse |
| Переход в директорию | cd /var/discourse |
| Клонирование репозитория | git clone https://github.com/discourse/discourse_docker.git |
| Директория контейнеров | mkdir -p /var/discourse/containers |
| Общие логи | mkdir -p /var/discourse/shared/web-only/log/var-log |
| Ссылка на контейнеры | ln -sfn /var/discourse/containers /var/discourse/discourse_docker/containers |
| Ссылка на launcher | ln -sfn /var/discourse/discourse_docker/launcher /var/discourse/launcher |
- КОНТЕЙНЕРЫ ПРИЛОЖЕНИЙ
app1.yml
• web + sidekiq
• порт 8001
docker_args: "--network=discourse-net"
expose:
- "8001:80"
app2.yml
• только web
• порт 8002
• sidekiq отключен
docker_args: "--network=discourse-net"
expose:
- "8002:80"
run:
- exec: bash -lc 'mkdir -p /etc/service/sidekiq && touch /etc/service/sidekiq/down'
- ИНИЦИАЛИЗАЦИЯ (BOOTSTRAP)
| Шаг | Команда |
|---|---|
| Переход в директорию | cd /var/discourse/discourse_docker |
| Инициализация app1 | ./launcher bootstrap app1 |
| Запуск app1 | ./launcher start app1 |
| Инициализация app2 | ./launcher bootstrap app2 |
| Запуск app2 | ./launcher start app2 |
- ПРОВЕРКИ РАБОТОСПОСОБНОСТИ
| Шаг | Команда |
|---|---|
| app1 | curl -sSf http://127.0.0.1:8001/srv/status |
| app2 | curl -sSf http://127.0.0.1:8002/srv/status |
| sidekiq app1 | docker exec -it app1 pgrep -fa sidekiq |
| sidekiq app2 | `docker exec -it app2 pgrep -fa sidekiq |
- TLS СЕРТИФИКАТ
| Шаг | Команда |
|---|---|
| Остановка прокси | systemctl stop haproxy |
| Выпуск сертификата | certbot certonly --standalone -d example.com --agree-tos -m you@example.com --non-interactive |
| Запуск прокси | systemctl start haproxy |
- ЛОГИКА HAPROXY
frontend fe_discourse
bind :80
bind :443 ssl crt /etc/letsencrypt/live/example.com/haproxy.pem
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
redirect scheme https code 301 if !{ ssl_fc }
use_backend be_discourse if { nbsrv(be_discourse) gt 0 }
default_backend be_maint
backend be_discourse
balance roundrobin
option httpchk GET /srv/status
server app1 127.0.0.1:8001 check
server app2 127.0.0.1:8002 check
backend be_maint
http-request return status 503 content-type text/html string "<h1>Maintenance</h1>"
- ПЕРЕСОЗДАНИЕ БЕЗ ПРОСТОЯ (ZERO-DOWNTIME)
| Шаг | Команда |
|---|---|
| Отключение app1 | echo "disable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock |
| Пересоздание app1 | ./launcher rebuild app1 |
| Включение app1 | echo "enable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock |
| Шаг | Команда |
|---|---|
| Отключение app2 | echo "disable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock |
| Пересоздание app2 | ./launcher rebuild app2 |
| Включение app2 | echo "enable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock |
КОНЕЦ
Требуется сеть Docker
Внешние PostgreSQL и Redis
Установлено pgvector
Sidekiq изолирован в app1
Включены проверки работоспособности HAProxy
Активен резервный режим обслуживания
Поддерживается последовательное пересоздание
Миграция одного сайта на собственный сервер в будущем
Одним из преимуществ запуска полностью независимых установок Discourse (вместо multisite) является то, что миграция проста и сопряжена с низким риском.
Каждый экземпляр Discourse уже имеет:
• собственный контейнер
• собственные загрузки (uploads)
• собственную базу данных
• собственное использование Redis
• собственный app.yml
Не требуется развязывание multisite.
Общие шаги миграции
- Подготовка нового VPS
Установите Docker и Discourse обычным образом на новом сервере.
Не настраивайте multisite.
- Создание полной резервной копии
На исходном сайте:
Администрирование → Резервные копии → Создать резервную копию
Скачайте файл резервной копии.
Она включает:
• базу данных
• загрузки
• пользователей
• настройки
• темы
- Восстановление на новом сервере
На новом сервере:
• выполните первоначальную настройку
• войдите как администратор
• загрузите резервную копию
• восстановите данные
Discourse автоматически обрабатывает совместимость схемы.
- Переключение DNS
Обновите A-запись домена, чтобы она указывала на IP-адрес нового сервера.
После обновления DNS пользователи будут автоматически перенаправлены.
- Отключение старого контейнера
На исходном сервере:
• остановите старый контейнер
• удалите его, когда будете уверены
Другие установки Discourse на том же хосте не пострадают.
Почему это проще, чем multisite
В настройках multisite миграция часто требует:
• разделения баз данных
• извлечения данных, специфичных для сайта
• настройки multisite.yml
• перестройки Sidekiq
• перенастройки загрузок и электронной почты
При использовании независимых установок этого не требуется.
Каждый сайт уже является независимым.
Резюме
Этот подход требует немного большей сложности в операционной деятельности на начальном этапе,
но обеспечивает очень простое разделение в будущем.
Он особенно хорошо подходит для экспериментов
или создания сообществ на ранней стадии.
Когда этот подход, вероятно, не подойдет
Эта настройка обычно не является хорошей идеей, если:
• сайты ожидают умеренный или высокий трафик на раннем этапе
• вы сильно полагаетесь на официальную поддержку Discourse
• вам неудобно отлаживать Docker, сети или обратные прокси
• требования к времени безотказной работы строгие или критичны для бизнеса
• несколько сайтов тесно связаны операционно
• вы ожидаете частых экспериментов с плагинами на всех экземплярах
В этих случаях лучше выбрать:
• поддерживаемую настройку multisite
или
• одну установку Discourse на сервер
что обычно приведет к меньшему количеству операционных неожиданностей.
Важное примечание
Этот подход увеличивает гибкость инфраструктуры,
но также увеличивает ответственность администратора.
Он работает лучше всего, когда человек, его использующий, готов взять на себя полный стек технологий
и воспринимать периодические сбои как часть процесса обучения.
Если стабильность и возможность поддержки являются основными целями,
поддерживаемая конфигурация почти всегда будет лучшим выбором.