単一のDiscourseサイトのための複数のアプリコンテナ

単一のサーバー上で、Discourse の「マルチサイト」機能を使用せずに、複数のスタンドアロン Discourse インストールをホストできます(別コンテナ / 別ポート / 別 app.yml)。

マルチサイトよりも手動での設定が必要ですが、インスタンスを分離でき、後で個別のサイトを独自のサーバーに移行しやすくなります。

実用的なパターンは以下の通りです:

• 外部 Postgres(単一インスタンス)
• 外部 Redis(単一インスタンス)
• 複数の Discourse Web コンテナ
• 1 つの Sidekiq ノード
• ヘルスチェック付きリバースプロキシ

これにより、マルチサイトを完全に回避しつつ、トラフィックの少ない設定でのコスト削減が可能になります。



DISCOURSE マルチコンテナ実行マニュアル
外部 Postgres + Redis + HAProxy + app1 / app2


  1. ホストパッケージ
ステップ コマンド
システムの更新 apt-get update
基本ツールのインストール apt-get install -y ca-certificates curl gnupg lsb-release
HAProxy + certbot + socat のインストール apt-get install -y haproxy certbot socat

  1. DOCKER ネットワーク(必須)

コンテナ間で名前解決を行うために、ユーザー定義の Docker ネットワークが必要です。

ステップ コマンド
ネットワークの作成 docker network create discourse-net
確認 docker network ls | grep discourse-net

これにより、以下が正しく機能します:

• DISCOURSE_DB_HOST=pg
• DISCOURSE_REDIS_HOST=redis


  1. 秘密鍵
目的 コマンド
Postgres 超級ユーザー export PG_SUPERPASS='REPLACE_ME_super_strong'
Discourse DB パスワード export DISCOURSE_DBPASS='REPLACE_ME_discordb_strong'
Redis パスワード export REDIS_PASS='REPLACE_ME_redis_strong'
Secret key base export SECRET_KEY_BASE="$(openssl rand -hex 64)"

  1. 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

  1. データベースの作成
ステップ コマンド
ロールの作成 docker exec -it pg psql -U postgres -c "CREATE ROLE discourse LOGIN PASSWORD '$DISCOURSE_DBPASS';"
DB の作成 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;"

  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';"

  1. 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

  1. 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
ランチャーのリンク ln -sfn /var/discourse/discourse_docker/launcher /var/discourse/launcher

  1. アプリケーションコンテナ

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'

  1. ブートストラップ
ステップ コマンド
移動 cd /var/discourse/discourse_docker
app1 のブートストラップ ./launcher bootstrap app1
app1 の起動 ./launcher start app1
app2 のブートストラップ ./launcher bootstrap app2
app2 の起動 ./launcher start app2

  1. ヘルスチェック
ステップ コマンド
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

  1. TLS 証明書
ステップ コマンド
プロキシの停止 systemctl stop haproxy
証明書の発行 certbot certonly --standalone -d example.com --agree-tos -m you@example.com --non-interactive
プロキシの起動 systemctl start haproxy

  1. 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>"

  1. ダウンタイムゼロの再構築
ステップ コマンド
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 ネットワークが必要
外部 Postgres および Redis
pgvector のインストール済み
Sidekiq は app1 のみに分離
HAProxy のヘルスチェック有効
メンテナンスフォールバック有効
ローリング再構築対応

後で 1 つのサイトを独自のサーバーに移行する

マルチサイトの代わりに完全にスタンドアロンの Discourse インストールを実行する利点の 1 つは、移行が簡単でリスクが低いことです。

各 Discourse インスタンスは既に以下を持っています:

• 独自のコンテナ
• 独自のアップロード
• 独自のデータベース
• 独自の Redis 使用
• 独自の app.yml

マルチサイトの分離は不要です。


高レベルの移行ステップ

  1. 新しい VPS のプロビジョニング

新しいサーバーで Docker と Discourse を通常通りインストールします。
マルチサイトは設定しないでください。


  1. 完全バックアップの作成

ソースサイトから:

管理者 → バックアップ → バックアップの作成

バックアップファイルをダウンロードします。

これには以下が含まれます:

• データベース
• アップロード
• ユーザー
• 設定
• テーマ


  1. 新しいサーバーでの復元

新しいサーバーで:

• 初期設定を完了
• 管理者としてログイン
• バックアップをアップロード
• 復元

Discourse はスキーマの互換性を自動的に処理します。


  1. DNS の切り替え

ドメインの A レコードを新しいサーバーの IP に更新します。

DNS が伝播すると、ユーザーは透過的に移動します。


  1. 古いコンテナの廃止

元のサーバーで:

• 古いコンテナを停止
• 確信が得られたら削除

同じホスト上の他の Discourse インストールには影響しません。


なぜこれがマルチサイトより簡単なのか

マルチサイト設定では、移行には通常以下が必要です:

• データベースの分離
• サイト固有データの抽出
• multisite.yml の調整
• Sidekiq の再構築
• アップロードとメールの設定変更

スタンドアロンインストールでは、これらは不要です。

各サイトは既に独立しています。


まとめ

このアプローチは、初期の運用複雑さを少し増やす代わりに、後の非常に簡単な分離を実現します。

実験中や初期段階のコミュニティ構築において特に効果的です。


このアプローチが適していない場合

以下の場合は、この設定は通常推奨されません:

• サイトが早期に中規模または高トラフィックを想定している場合
• 公式 Discourse サポートに大きく依存している場合
• Docker、ネットワーク、リバースプロキシのデバッグに不慣れな場合
• 稼働時間の要件が厳格またはビジネスクリティカルな場合
• 複数のサイトが運用面で密接に結合している場合
• 全インスタンスで頻繁にプラグインの実験を予定している場合

これらの場合は、以下のいずれかが通常、運用上の驚きを減らします:

• サポートされたマルチサイト設定
または
• サーバーごとに 1 つの Discourse インストール


重要な注意点

このアプローチはインフラストラクチャの柔軟性を高めますが、同時に管理者の責任も増大します。

これは、フルスタックを所有することに慣れ、偶尔の故障を学習プロセスの一部として扱うことができる人が実行する場合に最も効果的です。

安定性とサポート可能性が主要な目標である場合、サポートされる構成がほぼ常に優れた選択です。

上記のセットアップのHAProxy部分に直接関連する追加の注意点が1つあります。

HAProxyとDiscourseでは、Webコンテナを再構築する際(例:./launcher rebuild app1)、HAProxyがバックエンドにトラフィックを送信し続けている間に再起動するため、一時的に503 Service Unavailableの応答が返されるという一般的な動作があります。これはDiscourse自体のエラーではなく、バックエンドが再構築中に一時的に利用できなくなるために発生します。

推奨される回避策は、HAProxy管理ソケットを使用して次の操作を行うことです。
\t1.\t再構築の前にHAProxyでサーバーを無効にする、および
\t2.\t再構築が完了した後に再度有効にする

これにより、一時的な503エラーを防ぐことができます。

この動作と回避策の説明を文書化した既存のMetaディスカッションがあります。

ローリング再構築にHAProxyを使用している方は、そのスレッドが、実行手順書に管理ソケットコマンドが含まれている理由について役立つ背景情報を提供しています。

私も同様のことを行っており、サイトごとにWeb専用スタイルのコンテナを一つ用意し、リバースプロキシとしてtraefik(ただし、nginx-proxyを使った設定も試したことがあります)を使用しています。一時的にHAproxy(私が知る限りCDCKが使用しているもの)を試しましたが、煩雑だと感じました。

Discourseサーバーごとに1つのRedisが必要だと確信しています。

ここで用語の不一致があるかもしれません。

「Discourse サーバーごとに 1 つの Redis」と言う場合、サーバーを 1 つの論理的な Discourse サイトと見なすのであれば、私も同意します。

私のケースでは:

  • HAProxy はフェイルオーバー/フロントのためだけに
    使用されています
  • マルチサイト構成はありません
  • Discourse サイトは 1 つだけです(単一のホスト名、単一の Postgres DB)
  • たまたま同じサイトを提供できる 2 つのアプリコンテナが存在するだけです

したがって、これはマルチウェブ/HA レイアウトに近く、2 つの独立した Discourse インストールではありません。

そのセットアップでは、Redis を共有することが期待されており、必要です。そうしないと、次のものが失われます。

  • 共有セッション
  • MessageBus 配信
  • レート制限
  • バックグラウンドジョブの調整

これは、複数の web_only コンテナを実行したり、ウェブワーカーを水平にスケーリングしたりする場合と同じパターンです。
複数のアプリコンテナ → 1 つの Postgres + 1 つの Redis。

Redis を共有してはならないのは、2 つの別々の Discourse サイト(異なるホスト名/データベース)がある場合です。その場合、キーの競合を避けるために、各サイトは独自の Redis DB(またはインスタンス)を必要とします。

したがって、概念的には一致していると思います。

  • :white_check_mark: Discourse サイトごとに 1 つの Redis
  • :cross_mark: 個々のアプリコンテナごとに 1 つの Redis

もし私が何か誤解しているようでしたら、さらに明確にさせていただきます。トポロジーをより明確に説明したかっただけです。

ああ。それは私が考えていたことと正反対ですね。タイトルは「1台のサーバーで2つのDiscourseコミュニティ」ですが、あなたは「1つのDiscourseコミュニティに2台のサーバー」について話しています。

おっしゃる通りです。2つの異なるトポロジーを混同していました。スレッドのタイトルがその手がかりになります。

このトピックは「1台のサーバーで2つのコミュニティ」(2つの独立したサイト)に関するものです。
私が以前述べた「外部Redis(単一インスタンス)」に関するコメントは、別のパターン、すなわち**「1つのコミュニティに対する2つのアプリコンテナ」**(単一サイトのHA/マルチウェブ)を説明していました。

したがって、明確に再述します。

A) 1台のサーバー上の2つの独立したDiscourseサイト(OPが尋ねていること)

  • 2つの別個のインストールとして扱います。
  • 別々のPostgres DBと別々のRedisインスタンスを持つ必要があります(または、引用された落とし穴であるMessageBus Pub/Subに対して十分な分離が必要です)。

B) 複数のウェブ/アプリコンテナを持つ1つのDiscourseサイト(私が説明していたこと)

  • そのサイトのために、同じPostgres DBと、同じRedisを共有しなければなりません(セッション、レート制限、MessageBusなど)。

したがって::white_check_mark: あなたのRedisに関する警告はA(2つのコミュニティ/2つのサイト)に適用されます。
私の「共有Redis」に関する注記は、B(複数のコンテナにまたがる1つのコミュニティのスケールアウト)にのみ適用されます。

訂正ありがとうございます。今後のランブックや投稿では、この2つのケースを明確に区別するようにします。