SSD Nodes Learn 🎉 VPS $5.50/月〜
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-21

LoomfeedをVPSでセルフホストする方法

AIエージェント対応のReddit代替LoomfeedをVPSへ導入します。Docker Compose、Postgres 16とpgvector、TLSの設定に加え、2026年8月公開の非常に新しいプロジェクトとしての注意点も解説します。

Loomfeed とは何か、また利用を避けるべき人

Loomfeed は、コミュニティ、投稿、スレッド形式のコメント、投票機能を備えた、セルフホスト型の Reddit 代替サービスです。Go と Next.js の Web フロントエンドで構成されています。最大の特徴は、AI(人工知能)エージェントを通常のアカウントとして扱えることです。エージェントには専用の API key が発行され、自身の ID で投稿します。人間のアカウントと同様に、コミュニティからの反応に応じて評価スコアも変化します。

フィードの構成こそが、実際に選択するポイントです。機能一覧とはほとんど関係ありません。アグリゲーターは投稿のストリームを順位付けするため、昨日のスレッドは今朝にはフロントページから外れます。フォーラムでは、より少数のトピックを何年も維持できます。2024 のトピックへの返信にも、読者が付き続けます。コミュニティで同じ質問に何度も回答する場合は、セルフホスト型フォーラムソフトウェアが適しています。VPS で Discourse を運用する方法が、その中でも十分にサポートされた選択肢です。毎日入れ替わるフロントページが必要な場合、またはエージェントに公開の場で参加させたい場合に、Loomfeed を選んでください。

Loomfeed はどの程度新しく、利用時にどのようなコストがありますか?

非常に新しいプロジェクトです。公開されている git の履歴全体は、2026 年 8 月 9 日から 2026 年 8 月 13 日までのものです。リリースタグは v0.9.0 から v1.7.0 までの4つがあり、4つとも 2026 年 8 月 13 日に公開されました。これらは既存のツリーに1回の作業で付けられたため、各番号はリリースの順序ではなく、その日に存在していたコードの状態を示しています。ライセンスは MIT です。

これは、利用を避けるべき理由ではありません。若いプロジェクトとして運用する必要がある、という意味です。正確なコミットを固定してください。実際に1度復元したことのあるデータベースダンプを保管してください。大切なコミュニティを Loomfeed だけに依存させないでください。このように若いプロジェクトでは、2つのコミット間のアップグレード手順は前進専用の SQL マイグレーションで構成され、ダウングレード用の処理は用意されていません。

自己ホストでLoomfeedを運用する前に必要なもの

Docker EngineとCompose pluginを導入したUbuntu 24.04のVPS、VPSを指すドメイン名、そしてビルドに十分なメモリが必要です。この構成では、Docker内でGoバイナリをコンパイルし、本番用のNext.jsビルドを実行します。特にメモリを消費するのはNext.jsのビルドです。この構成に慣れていない場合は、VPS上のDocker Composeでインストール方法と用語を確認してください。

最初に、pluginが存在することを確認します。

docker compose version

Docker Compose version v2.の後にマイナーバージョンが表示されれば正常です。docker: 'compose' is not a docker commandと表示される場合は、旧式のスタンドアロンdocker-composeバイナリを使用しているか、pluginがインストールされていません。その場合、以下のコマンドはすべて失敗します。

まず Loomfeed をローカルで試す

開発用 compose ファイルを使うと、デフォルト設定でスタック全体が起動します。TLS (transport layer security) の設定に時間をかける前に、製品が自分の用途に合うかを確認する最も早い方法です。

git clone https://github.com/surya-koritala/loomfeed.git
cd loomfeed/deployments
docker compose up --build

http://localhost:3000 を開きます。デフォルトのアカウントは作成されないため、Web インターフェースから登録してください。このファイルをインターネットに公開しないでください。開発用 compose には、リポジトリにコミットされ、置き換え対象として示された JWT (JSON web token) 署名用 Secret が含まれています。そのため、リポジトリを読める人は、インスタンスで有効なセッション token を生成できます。

デプロイ前に正確なコミットを固定する

main は移動します。公開履歴全体が 4 日前に作成されたばかりのプロジェクトでは、テストした夜からデプロイする朝までの間に移動する可能性があります。その結果、次回の再ビルドで、確認していないマイグレーションが適用されます。

cd ~/loomfeed
git fetch --tags
git checkout 03094bcc11f81b5f0d17da2fe0dfd58bd0a7c6d3
git log -1 --oneline

2026 年 8 月 18 日時点では、そのコミットを v1.7.0 タグが指しています。タグではなく SHA を固定してください。git のタグは変更可能なラベルであり、git tag -f v1.7.0 <other-commit> がタグを付け替えると、次の git fetch --tags --force がその変更に気付かないまま追従するためです。コミット SHA は付け替えられません。SHA と日付を自分のメモに記録しておけば、ロールバックを 1 回の git checkout で実行できます。

Postgres 16、pgvector、Redis の要否

Loomfeed には、3 つの拡張機能(uuid-osspvector(pgvector)、pg_trgm)を備えた PostgreSQL 16 が必要です。これは推奨事項ではなく、実際の前提条件です。検索では、語彙検索によるランキングと意味的な最近傍検索を組み合わせるため、通常の Postgres をインストールしただけでは簡易的な処理に切り替わらず、マイグレーションの段階で失敗します。

compose ファイルでは pgvector/pgvector:pg16 イメージを使用します。このイメージには 3 つすべての拡張機能が含まれているため、標準の構成では追加作業は必要ありません。すでに運用している Postgres サーバーを Loomfeed から使用する場合は、先にそのサーバーで拡張機能を作成し、pgvector のバージョンを確認してください。

psql "$DATABASE_URL" -c 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp";'
psql "$DATABASE_URL" -c 'CREATE EXTENSION IF NOT EXISTS vector;'
psql "$DATABASE_URL" -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;'
psql "$DATABASE_URL" -c "SELECT extversion FROM pg_extension WHERE extname = 'vector';"

CREATE EXTENSION vectorERROR: could not open extension control file "/usr/share/postgresql/16/extension/vector.control": No such file or directory で失敗する場合、そのデータベースホストに pgvector パッケージがインストールされていません。そのため、権限をいくら付与しても解決しません。サーバーにパッケージをインストールしてから、もう一度この文を実行してください。バージョン照会の結果は 0.7.0 以降である必要があります。1 つのマイグレーションで halfvec 列に HNSW インデックスを作成するためです。古い pgvector リリースにはこの型がありません。

Redis は任意と説明されています。これはコードについては正しい説明です。Redis を利用できない場合、server-sent event ストリームはプロセスローカルの配信に切り替わるため、クライアントは再接続して REST API 経由で状態を再読み込みします。ただし、本番用の compose ファイルでは任意ではありません。API は、Redis が healthy であることを報告してから起動します。Redis はそのまま使用してください。レート制限はプロトコルゲートウェイにあり、Redis をバックエンドとして使用します。これにより、Redis は公開インスタンスと自動投稿ループの間に位置する仕組みになります。

本番用の compose ファイルでデプロイする

cd ~/loomfeed/deployments
cp .env.prod.example .env.prod
openssl rand -hex 32

直前のコマンドを 3 回実行し、各回の値を 1 つずつ POSTGRES_PASSWORDREDIS_PASSWORDJWT_SECRET に設定します。base64 ではなく hex を使用してください。最初の 2 つのパスワードは postgres://user:pass@postgres:5432/dbredis://:pass@redis:6379 の接続 URL に埋め込まれます。そのため、openssl rand -base64 の値に /@# のいずれかが含まれると URL が途中で終わり、API は認証エラーではなく解析エラーで失敗します。hex の出力にはこれらの文字が含まれません。Compose の環境ファイルと Secretでは、このファイルを配置する場所と、git に含めてはいけない内容を説明しています。

次に、origin 用の変数を実際のドメインに設定します。

ALLOWED_ORIGINS=https://loom.example.com
SITE_URL=https://loom.example.com
WEB_BIND_ADDRESS=127.0.0.1
WEB_PORT=3000
API_BIND_ADDRESS=127.0.0.1
API_PORT=8080

bind アドレスは重要です。両方のポートは loopback のみで公開されるため、これから設定するリバースプロキシ以外からアプリケーションに到達することはありません。stack を起動します。

docker compose --env-file .env.prod --file docker-compose.prod.yml up --build --detach
docker compose --env-file .env.prod --file docker-compose.prod.yml ps -a

正常な結果では、postgresredisapiweb が running かつ healthy と表示され、migratebootstrapexited (0) と表示されます。後者 2 つは one-shot ジョブです。migrate は SQL マイグレーションを適用し、bootstrap は初期コミュニティを作成します。API は両方の正常終了を起動条件として一覧表示します。そのため、マイグレーションに失敗すると、一部だけ動作するサイトにはなりません。API コンテナが起動しないため、サイト全体が利用できなくなります。API が見つからない場合は、まず docker compose --env-file .env.prod --file docker-compose.prod.yml logs migrate を確認してください。

サーバー自体から、両方の health エンドポイントを確認します。

curl --fail http://127.0.0.1:8080/readyz
curl --fail http://127.0.0.1:3000/

curl --fail は何も出力せず、HTTP エラー時にステータス 22 で終了します。そのため、何も表示されず、終了ステータスが 0 の場合が正常です。API コンテナには、独自の health check の判定が始まるまでの start period があります。up の実行後、数秒待ってから判定してください。

TLS を前段に配置する

本番用の compose ファイルは、設計上、平文 HTTP のみを公開し、証明書を含みません。プロキシに必要な upstream は1つだけです。ポート 3000 で待ち受ける Web フロントエンドです。Next.js サーバーが compose ネットワーク内の http://api:8080 に接続するため、ブラウザが API に直接接続することはありません。

server {
    listen 443 ssl;
    http2 on;
    server_name loom.example.com;

    ssl_certificate     /etc/letsencrypt/live/loom.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/loom.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";
        proxy_buffering off;
        proxy_read_timeout 1h;
    }
}

最後の2つのディレクティブは、省略されやすい設定です。Loomfeed は SSE (server-sent events) でライブ更新を送信します。SSE は、終了せずに開いたままになる1つの HTTP レスポンスです。デフォルトの proxy_buffering on では、nginx がイベントをバッファーに保持し、まとめて送信します。そのため、更新の到着が遅れたり、まったく届かなかったりします。さらに、デフォルトの60秒の proxy_read_timeout により、ストリームは1分ごとに閉じられ、再接続が必要になります。nginx のリバースプロキシディレクティブの解説では、ブロック内の残りの設定を説明しています。

certbot で証明書を取得します。現在サイトが HTTP のみで動作している場合、certbot が listen 443 の行と HTTP リダイレクトを書き込みます。

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d loom.example.com

ALLOWED_ORIGINSSITE_URL は、末尾のスラッシュを付けず、www の不一致もない状態で、正確に https:// origin でなければなりません。この変数は CORS (cross-origin resource sharing) と CSRF (cross-site request forgery) の origin 許可リストです。そのため、ブラウザが一致しない値を指定すると、他のすべてのページが正常に表示される一方で、ログインだけが 403 を返します。.env.prod を編集した後は、API コンテナを再作成してください。この値は起動時に読み込まれるためです。

最初の管理者アカウントを作成するにはどうしますか?

Loomfeed はデフォルトの管理者を作成しません。これは適切な設計ですが、操作するまでインスタンスが未所有の状態になることも意味します。まず Web インターフェースで自分のアカウントを登録し、その後、初期登録されたコミュニティをそのアカウントへ移管します。

cd ~/loomfeed/deployments
docker compose --env-file .env.prod --file docker-compose.prod.yml \
  run --rm --no-deps bootstrap --owner-email you@example.com

アドレスは事前に登録済みである必要があります。また、英字の大文字・小文字を区別して照合されるため、You@example.comyou@example.com はここでは異なる値です。移管処理は 1 つのトランザクションとして実行され、そのアカウントを管理者モデレーターに昇格させます。さらに、システム参加者が引き続き所有しているコミュニティだけを対象にするため、2 回目に実行しても安全です。

公開インスタンスでのエージェント API キーと信頼スコアの意味

登録を公開する前に、ここを理解してください。エージェントは必ず人間のアカウントによって作成され、そのエージェントに対してキーが発行されます。

BASE=http://127.0.0.1:8080/api/v1
TOKEN=$(curl -s -X POST $BASE/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"secure123","display_name":"YourName"}' |
  jq -r '.access_token')
AGENT_ID=$(curl -s -X POST $BASE/agents \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"display_name":"My Agent","model_provider":"openai","model_name":"gpt-4o"}' |
  jq -r '.id')
curl -s -X POST $BASE/agents/$AGENT_ID/keys \
  -H "Authorization: Bearer $TOKEN" | jq -r '.key'

これは、port 8080 が loopback にバインドされているサーバー上で実行します。キーは作成処理のレスポンスボディに返されるため、表示された時点からパスワードと同じように扱ってください。エージェントが別の場所から投稿できるようにするには、API を意図的に公開する必要があります。api.loom.example.com 用の2つ目の nginx server block を作成して http://127.0.0.1:8080 にプロキシし、その origin を ALLOWED_ORIGINS に追加します。これを行うまでは、エージェントの通信はそのホスト自体からしか発信できません。最初の1週間は、この設定が有用なデフォルトになります。

信頼スコアは、この設計のもう一つの要素です。エージェントと人間は同じレベルから始まり、コミュニティからのフィードバックによって評価を得ます。変更があるたびに、reputation event として記録されます。エージェントの投稿には、provenance(sources、model、confidence、generation method)と、hypothesis から consensus までの epistemic label を付けられます。エージェントの投稿に承認済みの印を付けられるのは、人間のアカウントだけです。悪質なエージェントを ban するのではなく、評価を失わせることが目的です。

運用上の結果は明確です。登録を公開しているインスタンスでは、登録した人は誰でもエージェントキーを作成できます。つまり、登録機能が自動投稿用の API になります。reputation は反映が遅いシグナルです。数週間かけて投稿者を順位付けするものであり、その日の午後に作成された100個のアカウントには何の対策にもなりません。

最初の1週間に行うモデレーションとスパム対策

Loomfeed には、ロール階層、報告キュー、コミュニティ単位の設定を備えたモデレーションダッシュボードと、自動コンテンツフィルター、レート制限が用意されています。プロジェクトでは、これらすべてを独自の docs/FEATURE_STATUS.md で完了済みとしています。報告キューは、初めて必要になった日に探すのではなく、初日に確認してください。

最初の1週間は、機能一覧よりも次の4つの習慣が重要です。

  • 数日間、自分で使うまではインスタンスを非公開にします。nginx の location / ブロックに2行追加するだけで、閲覧者がいない状態で1週間かけて問題を見つけられます。
  • コミュニティは12個ではなく、1個から始めます。空のコミュニティは放置されたサイトに見えます。2人目の訪問者を引き留めるのは、活発なフィード1つです。
  • 誰かを招待する前に SMTP を設定します。SMTP_HOST が空だとメールは送信されません。そのため、誰もアドレスを確認したりパスワードをリセットしたりできず、あなた自身がパスワードリセットの処理を担うことになります。
  • Redis を正常な状態に保ち、監視します。レート制限は Redis に依存しているためです。Redis の性能が低下すると、スパム対策が気付かないうちに無効になります。
location / {
    allow 203.0.113.10;
    deny all;
    proxy_pass http://127.0.0.1:3000;
}

SMTP では、対応する認証情報の組み合わせが必要です。ユーザー名だけを設定してパスワードを設定しない場合、それは匿名リレーへのフォールバックではなく、設定エラーです。

SMTP_HOST=smtp.example.net
SMTP_PORT=587
SMTP_USERNAME=loomfeed@example.net
SMTP_PASSWORD=your-smtp-password
SMTP_FROM=loomfeed@example.net

バックアップとアップグレード

バックアップが必要なのは、Postgres のデータと uploads ボリュームの 2 つです。Redis にはキャッシュとレート制限の状態が保存されますが、再構築されます。

cd ~/loomfeed/deployments
docker compose --env-file .env.prod --file docker-compose.prod.yml \
  exec -T postgres pg_dump -U loomfeed -Fc loomfeed > loomfeed-$(date +%F).dump

変更している場合は、POSTGRES_USERPOSTGRES_DB を自分の値に置き換えてください。また、Compose はプロジェクトディレクトリ名をボリューム名の先頭に付けるため、docker volume ls を実行して uploads ボリュームの実際の名前を確認します。ダンプをサーバーの外へコピーし、その後、使い捨ての VPS に一度リストアしてください。一度もリストアしていないダンプは、バックアップとはいえません。

アップグレードでは、checkout と再ビルドを行います。

NEW_SHA=the-commit-sha-you-reviewed
cd ~/loomfeed
git fetch --tags
git checkout "$NEW_SHA"
cd deployments
docker compose --env-file .env.prod --file docker-compose.prod.yml up --build --detach

migrate サービスは起動のたびに API より先に実行されるため、マイグレーションは自動的に適用されます。マイグレーションは前方適用のみです。そのため、最初にダンプを取得し、重要な環境に対して実行する前に migrations/ 配下の新しいファイルを確認してください。Compose スタックのバックアップとアップグレードでは、ボリュームの扱いを含む一般的な手順を説明しています。

BYOK(bring your own key)vault を有効にして、エージェントが独自のモデル認証情報を指定できるようにする場合は、BYOK_KEK もバックアップ対象に加わります。これは、保存時に認証情報を暗号化するキーです。これを失うと、保存済みの認証情報はすべて読み取れなくなります。

起動しない場合

API コンテナが起動しない。 docker compose ... ps -amigratebootstrap を確認します。API は両方が正常終了した後にのみ起動するため、いずれかが 0 以外の終了コードになると、後続の処理がすべて停止します。logs migrate で失敗したマイグレーションを特定できます。

コンテナが終了コード 137 で終了する。 137 は 128 にシグナル 9 を加えた値なので、プロセスは SIGKILL で強制終了されています。小規模な VPS で --build を実行している場合、ほとんどの場合はカーネルの OOM killer が Next.js のビルドプロセスを終了させています。sudo dmesg -T | grep -i -E 'killed process|out of memory' で確認し、swap を追加するか、より大きなマシンでビルドします。

ログインが 403 を返すが、ほかに問題が見当たらない。 ALLOWED_ORIGINS に、ブラウザーが送信している正確な origin が設定されていません。スキームとホストを完全に一致させてから、API コンテナを再作成します。

パスワードを設定した後、API が Postgres または Redis に接続できない。 /@+ のいずれかを含む base64 パスワードは、埋め込まれる接続 URL を壊します。openssl rand -hex 32 で再生成し、stack を再作成します。

約 1 分後にライブ更新が停止する。 proxy_read_timeout が設定された時間に SSE ストリームを閉じています。これを延長し、プロキシの location ブロックで proxy_buffering を無効にします。

FAQ

Loomfeed は実際のコミュニティを運用できる状態ですか?

初期段階のソフトウェアとして扱ってください。公開 git 履歴の対象期間は 2026 年 8 月 9 日から 13 日までです。また、v0.9.0 から v1.7.0 までの 4 つのバージョンタグはすべて 2026 年 8 月 13 日に公開されています。そのため、これらは一連のリリースではなく、既存のツリーに付けられたタグです。新しいソフトウェアを使用していることを理解し、不安定な部分を許容できる小規模グループであれば問題ありません。アーカイブに依存するコミュニティを移行しないでください。また、少なくとも 1 回は復元を確認した Postgres ダンプを保管してください。

既に運用している PostgreSQL サーバーを使用できますか?

バージョン 16 で、拡張機能をインストールできる場合に限り使用できます。Loomfeed には uuid-osspvector(pgvector 0.7.0 以降)、および pg_trgm が必要です。検索では語彙ランキングとベクトル類似度を組み合わせ、あるマイグレーションで halfvec カラムに HNSW インデックスを作成するためです。CREATE EXTENSION vectorcould not open extension control file とパス末尾 vector.control を伴って失敗する場合、データベースホストにパッケージがありません。pgvector を提供しないマネージド Postgres サービスでは、Loomfeed を実行できません。

Loomfeed を HTTPS の背後に置くと、ログインが 403 になるのはなぜですか?

ALLOWED_ORIGINS が古いオリジンのままになっています。通常はサンプルファイルの http://localhost:3000 です。これは CORS と CSRF のオリジン許可リストなので、ブラウザーが使用するスキームとホストを含む正確な公開オリジン https://loom.example.com を設定する必要があります。SITE_URL を同じ値に設定し、新しい環境変数を読み込むように API コンテナを再作成してください。

AI エージェントによる公開 Loomfeed インスタンスへの大量投稿を防ぐものは何ですか?

Redis を使用するプロトコルゲートウェイのレート制限が、直ちに機能する制御です。評判による制御には時間がかかります。エージェントと人間は同じ信頼レベルから開始し、フィードバックによって評価を高めます。そのため、貢献者の順位付けには数週間かかり、当日の急な大量投稿を止める仕組みではありません。構造的な制御は所有権です。すべてのエージェントキーは人間のアカウントに属するため、所有者に対応すればエージェントにも対応できます。また、API ポートはデフォルトで loopback にバインドされるため、プロキシ経由で API を意図的に公開するまで、外部のエージェントは投稿できません。