nessie

Nessieのインストール

1台のマシンから、マネージドクラウドまで、お選びの基盤でNessieを動かせます。

始める前に必要なもの

Nessieは、Nodeサービス群と2つの静的バンドルで構成されたpnpmモノレポです。どの方法で実行する場合も、次の4つを先に用意しておく必要があります。

  • PostgreSQL(pgvector拡張付き)。必須であり、代替はできません。マイグレーションはCREATE EXTENSION vectorを実行し、vector(1024)列を作成します。本番環境ではpgvector/pgvector:pg17を使用しています。
  • Node 22とpnpm。infrastructure/docker/内のすべてのDockerfileはnode:22-slimをベースにビルドします。また、リポジトリはルートのpackage.jsonでパッケージマネージャーをpnpm@10.22.0に固定しています。
  • OpenAI互換のモデルエンドポイントと、そのキー。エージェントと埋め込みは、どちらもこれを経由します。下記の「推論の設定」を参照してください。
  • S3互換のオブジェクトストレージ。ローカル開発環境以外のデプロイには必要です。localモード以外ではローカルディスク上のアップロードは拒否され、その拒否は機能を制限した状態での起動ではなく、起動失敗になります。詳しくは「1台のマシン、またはマネージドクラウド」を参照してください。

後半で説明するコンテナでの実行には、Dockerとリバースプロキシが必要です。インターネットからアクセスするものには、ドメインとTLSも必要です。

ハードウェアの規模(利用人数に応じた最小のCPU、メモリ、ディスク容量)は、リポジトリのどこにも記載されていないため、このページでも記載していません。

コンポーネントと、それらの連携

コンポーネント概要外部からの受信
api/Fastifyで構築されたHTTP/WebSocket APIです。すべてのクライアントはこのAPIとだけ通信します。はい
worker/エージェントのオーケストレーションとバックグラウンド実行を担います。ジョブキューをポーリングし、ポートはバインドしません。いいえ
admin/製品の管理画面です。Viteでビルドされた静的バンドルで、nginxが配信します。はい
web/公開サイトです。静的な構成で任意であり、製品の実行とは無関係です。はい
PostgreSQLデータベース、ジョブキュー、リアルタイムバスの3つすべてを担います。いいえ
オブジェクトストレージ添付ファイル、アバター、ナレッジベースのファイルを保存します。S3互換で、本番環境ではMinIOを使用します。いいえ
gateway/APNsとFCM向けのプッシュ中継(任意)です。明示的に有効にしない限り、無効のままです。はい

APIとワーカーは1つのイメージです。2つのパッケージは密接に連携しているため、Dockerfile.appはワークスペース全体を一度だけビルドし、ワーカーのコンテナではコマンドをnode worker/dist/index.jsに上書きします。localモードではワーカーのコンテナは存在せず、APIが自身のプロセス内に組み込まれたワーカーを実行します。

Redisは使用していません。ジョブキューとリアルタイム通信の基盤は、どちらもPostgresで実装しています。これは省略ではなく、意図した設計です。Google Cloud向けのツリーも、同じ理由でPub/SubとRedisのモジュールを削除しています。

ポート

ローカル開発では、APIは既定で5454、管理画面は5455を使用します。チェックアウトごとに、環境変数、またはリポジトリの.envでNESSIE_API_PORTとNESSIE_ADMIN_PORTを設定すれば、独自のポートの組を使えます。ブラウザのテストスイートを含め、リポジトリ内のすべてのツールが同じ2つの値を読み取ります。起動前に組を決めて、そのまま使い続けてください。稼働中のサービスを別のポートに移すと、管理画面が誤ったAPIと通信することになります。

本番環境は別の扱いで、このルールの影響は受けず、変更はありません。APIコンテナは内部で5554を待ち受け(NESSIE_API_PORTで固定)、プッシュ中継は5556を待ち受けます。どちらもリバースプロキシの背後に置かれます。

1台のマシンでの開発用の実行

リポジトリには、データベースだけを含むComposeファイルが用意されています。ローカルでの開発に必要なのはこれだけです。

pnpm install
docker compose -f infrastructure/compose/docker-compose.yml up -d postgres

そのコンテナは、ユーザー名、パスワード、データベース名がすべてnessieのPostgresを55432で公開します。接続文字列はリポジトリのルートにある.envに記述してください。APIの開発スクリプトがこのファイルを読み込みます。その後、マイグレーションを適用し、ワーカーを一度ビルドします。ローカルモードでは、APIがビルド済みのdistからワーカーを読み込むためです。

DATABASE_URL=postgresql://nessie:nessie@localhost:55432/nessie

pnpm --filter @nessie/api prisma:migrate:deploy
pnpm --filter @nessie/worker build
pnpm dev

pnpm devを実行すると、APIと管理画面がホットリロード付きで並行して起動し、ポートは5454と5455になります。GET /api/healthにアクセスして、APIが応答することを確認してください。

Postgresとpgvectorの要件

マイグレーションが作成する拡張機能は3つあります。vector、pg_trgm、pgcryptoです。そのため、マイグレーションを実行するロールに拡張機能の作成権限が必要です。マネージドのPostgresを使う場合は、まずこの点を確認してください。

ベクトル次元数はスキーマの一部であり、設定項目ではありません。thoughts.embedding、thought_recalls.query_embedding、knowledge_page_chunks.embeddingはvector(N)列で、Nはpackages/schemas/src/embedding.tsのEMBEDDING_DIMENSIONSに一度だけ定義されています。現在の値は1024で、本番環境が使用している埋め込みモデルのネイティブな次元数です。埋め込みのリクエストはすべてこの次元数を送信するため、別の次元数で応答するプロバイダーは、データベースが受け付けないベクトルを書き込むのではなく、明確にエラーになります。別の次元数の埋め込みモデルに移行するには、その定数を編集し、3つの列の型を変更するマイグレーションを書き、再埋め込みを行う必要があります。異なる次元数のベクトルは変換できないため、マイグレーションは切り詰めずにNULLにします。

Nessieを他のアプリケーションと共有のDockerネットワーク上で動かす場合は、そのデータベースのコンテナに別の名前を付けてください。このプロジェクトのComposeサービス名はnessie-postgresで、postgresは使用しません。サービス名は共有ネットワーク上のDNSエイリアスになるため、名前が衝突するとpostgres:5432が2つのデータベースの間でラウンドロビンされてしまいます。

設定

実行時の設定は階層化されています。APIとワーカーには読み取り専用でマウントされたnessie.config.jsonがあり、その上に環境変数が重ねられます。両者の対応関係はpackages/configのConfigEnvMapで定義されており、これが正式な一覧です。

NESSIE_MODEはデプロイの形態を選びます。local(既定値で、開発者が使う形態)、hosted、selfHostedのいずれかです。これは、セルフホストのインストールが設定する値です。このモードではdevログインが無効になり、CORSの許可リストが必要になります。

selfHostedのデプロイで、次の設定がないと起動しないか、正しく動作しません。

変数理由
DATABASE_URL / NESSIE_DB_URLPostgresの接続文字列です。
NESSIE_AUTH_SECRET16進数で32バイト(openssl rand -hex 32で生成)。セッションとブートストラップトークンに署名します。localモード以外では、この値がないと起動時にエラーになります。すべてのレプリカとデプロイをまたいで同じ値である必要があります。異なると、リリースのたびにすべてのセッションが無効になります。
NESSIE_ENCRYPTION_KEY_RING, NESSIE_ENCRYPTION_ACTIVE_KEY_VERSIONJSON形式のキーリングと、有効なルートを選ぶラベルです。localモード以外では必須です。これは署名用シークレットとは別のルートです。キーリングは、Nessieが保存するすべての永続的なシークレットを暗号化します。両者を同じ値で兼用しないでください。
NESSIE_STORAGE_PROVIDERs3である必要があります。filesystemが既定値ですが、hostedとselfHostedモードでは拒否されます。そのため、この項目を設定しないデプロイは、他のコンテナから読めないディスクにアップロードを書き込むのではなく、起動に失敗します。
NESSIE_CORS_ORIGINS管理画面のオリジンです。管理画面とAPIは別のオリジンであり、APIは提供する管理画面のオリジンを許可リストに登録する必要があります。
NESSIE_API_PUBLIC_URL, NESSIE_ADMIN_PUBLIC_URLリクエストの外でOAuthのリダイレクトURIを生成するために使用します。1つ目の値がない場合、APIはリクエスト時にエラーを投げます。
NESSIE_MODEL_PROVIDER, _BASE_URL, _API_KEY推論の送信先です。下記を参照してください。

infrastructure/compose/.env.prod.exampleには、すべての設定の正確な形式が記載されています。オプションのブロック(オブジェクトストレージ、シークレットボールト、SSO、接続済みメール)は、該当箇所にコメントとして示されています。すべてのオプション設定を含む完全な一覧はdocs/deployment/configuration.mdにあります。

Docker Composeでのデプロイ

.env.prod.exampleをdocker-compose.prod.ymlと同じ場所で.envにコピーし、内容を記入してください。その後、データベースを起動し、ビルド、マイグレーション、起動の順に進めます。順序が重要です。マイグレーションとシードは、どのサービスも応答を始める前に、起動済みのデータベースに対して実行されます。

COMPOSE="docker compose -f infrastructure/compose/docker-compose.prod.yml"

bash infrastructure/compose/ensure-encryption-key-ring.sh infrastructure/compose/.env
$COMPOSE up -d nessie-postgres
$COMPOSE build nessie-api nessie-admin nessie-web
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api prisma:migrate:deploy
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api seed:connectors
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api seed:apps
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api reconcile
$COMPOSE up -d

本番用のDockerfileは、各パッケージをビルドする前にそのパッケージのリントを実行します。そのため、リントが失敗すればビルドも失敗します。

reconcileステップは省略できません

reconcileは、各組織の既定のポリシールールをシードし、保護されたMCPとパーソナルアシスタントのツールの付与を補完します。以前はAPIが起動時にすべてのレプリカでこれを行っていましたが、現在のAPIは接続して待ち受けるだけで、ほかには何もしません。そのため、この手順がないと、最初の組織にはポリシールールが存在せず、すべてのエージェントのバインドが拒否されます。この処理は冪等なので、デプロイのたびに実行しても問題ありません。手作業で適用したアップグレードでも、同様に実行する必要があります。

最初のオーナーアカウント

新規のselfHostedインストールにはユーザーが存在しないため、APIは一度限りのブートストラップトークンを発行し、セットアップURLをログに出力します。トークンはプロセスではなくPostgresに保存されるため、すべてのレプリカが同じトークンを参照します。

docker compose -f infrastructure/compose/docker-compose.prod.yml logs nessie-api 2>&1 | grep bootstrap

https://<your-admin-host>/bootstrap?token=<token>を開き、オーナーアカウントを作成してください。トークンの有効期限は15分です。期限が切れたら、APIサービスを再起動して新しいトークンを発行してください。

SSOを設定する場合、ブートストラップの手順は一切ありません。外部の認証プロバイダーが有効になると、ブートストラップモードは直ちに抑止されます。そうしないと、ログイン画面が乗っ取られてしまうためです。SSOで最初にログインした人が既定のチームを作成し、そのチームのオーナーになります。

推論の設定

チャットはOpenAI互換です。NESSIE_MODEL_BASE_URLがエンドポイント、NESSIE_MODEL_API_KEYがBearerトークン、NESSIE_MODEL_PROVIDERがアダプターの名前です。Nessieはopenai、kimi、deepseekのアダプターを組み込んでおり、それ以外は汎用のOpenAI互換コネクタを経由して接続します。NESSIE_MODEL_NAMEはデプロイの既定モデルを設定します。ただし、個々のエージェントが自分で選んだモデルは、この設定より優先されます。

埋め込みは個別にルーティングされます。チャットのプロバイダーは埋め込みをまったく提供していない場合があるためです。たとえばDeepSeekには埋め込みのエンドポイントがありません。未設定のNESSIE_EMBEDDING_*の各項目は、チャットのプロバイダーの設定を引き継ぎます。そのため、これらをどれも設定しないデプロイは、チャットのエンドポイント経由で埋め込みを行います。チャットのプロバイダーが埋め込みを提供できない場合は、NESSIE_EMBEDDING_PROVIDERとNESSIE_EMBEDDING_MODELを設定してください。サービス単位でルーティングするプロキシを使う場合は、サービスのセグメントも設定します。この設定を誤ると、明確なエラーにはならず、静かに失敗します。メモリの想起とナレッジベースの検索は、語彙ベースの検索経路に縮退して動作を続け、その間kb_search: query embedding failedをログに出力します。

Nessie独自の支出の計算(組織ごとのトークン台帳と予算の制御)は、どの推論エンドポイントを指定しても影響を受けません。また、署名付きの呼び出し単位の帰属を設定しているかどうかに関係なく、同じように動作します。

ログイン

Nessieは、組織とチームの構造も管理するUnlikeOtherAIを通じて利用者を認証します。管理画面のログインページには「Login / Register」ボタンが1つだけ表示されます。

この連携は、標準のOIDCではなく、設定JWTのフローです。APIは署名付きのRS256設定ドキュメントをGET /api/auth/sso/configで提供し、対応するJWKSをGET /.well-known/jwks.jsonで提供します。どちらもAPIのホストで提供されます。セットアップは一度だけ行う手順です。まずRSA-2048の鍵ペアを生成し、UOA_CONFIG_JWT_PRIVATE_KEY_B64、UOA_CONFIG_JWT_KID、UOA_DOMAIN、UOA_CONFIG_URL、UOA_JWKS_URL、UOA_REDIRECT_URL、UOA_CONTACT_EMAILを設定します。次にボタンを一度クリックすると、連携リクエストが作成されます。それが承認されると、連絡先アドレス宛てに、クライアントシークレットを含む一度限りのリンクが届きます。UOA_CLIENT_SECRETを設定し、APIを再起動してください。kidは、ドメインごとに一意である必要があります。

ドメインとTLSの背後に置く

デプロイでは3つの名前を提供します。公開サイト、管理画面、APIです。管理画面のオリジンとAPIのオリジンは互換性がありません。Dockerfile.adminは、ビルド時にVITE_API_BASE_URLをバンドルに組み込みます。そのため、組み込みのデスクトップ版を含め、ビルド済みの管理画面はすべてAPIのオリジンに対してビルドする必要があります。管理画面自身のオリジンに対してビルドすると、/api/auth/providersが管理画面のHTMLシェルに解決され、ログインが「Loading providers...」のまま止まります。

本番環境では、TLSはCaddyで終端されます。Caddyは、ホスト名ごとの証明書をHTTP-01で取得するため、その用途にDNSプロバイダーのプラグインは不要です。プロキシは、ポート80の管理画面とWebのコンテナ、および内部ポートのAPIに向けてください。

NESSIE_API_TRUSTED_PROXY_HOPSには、APIの前にあるプロキシの数を設定します。リバースプロキシが1つなら1です。既定値は0で、その場合X-Forwarded-Forは完全に無視されます。この設定は一つの判断で、二つの結果をもたらします。認証のレート制限のキーにもなるため、値が小さすぎるとすべてのクライアントがプロキシのバケットを共有し、大きすぎるとクライアントが自分のアドレスを偽装できます。低めに設定し、実際に計測してください。

各組織とチームに専用のサブドメインを割り当てることは可能ですが、既定ではオフです。NESSIE_TEAM_HOST_BASE_DOMAINで管理されます。未設定の場合、テナントはホスト名でルーティングされません。証明書発行のゲートであるNESSIE_TLS_CHECK_KEYは、未設定の間すべてを拒否します。この機能を使わないデプロイにとっては、これが正しい既定値です。

ヘルスチェック

エンドポイント答える問い
GET /api/healthこのプロセスは生きているか。ドレインを開始した後にのみ失敗します。
GET /api/health/readyこのレプリカは新しいリクエストを受け付けてよいか。ロードバランサーの向け先として使うべきエンドポイントです。
GET /api/ops/healthデプロイの状態はどうか。人が読むためのレポートで、ワーカーのハートビート、キューの深さ、デッドジョブを含みます。プローブではありません。

SIGTERMを受け取ると、APIはその場で停止せず、ドレインを行います。NESSIE_SHUTDOWN_TIMEOUT_MSは、そのドレインの上限時間を定めます。既定値は25秒です。この値は、オーケストレーターがコンテナに与える猶予期間より短くしてください。そうしないと、ドレインが完了する前にランタイムがプロセスを強制終了し、ドレインの意味がなくなります。

アップグレード

アップグレードは、既存のデータベースに対するprisma migrate deploy、シードの実行、reconcileの順に行います。初回デプロイと同じ4つのコマンドです。

この手順は、新規のデータベースではなく、セルフホストの利用者が実際に取る経路に沿ってテストされています。CIは、20マイグレーション前に取ったスナップショットを復元し、現行の最新版までマイグレーションを適用して、スキーマを検証し、主要なテーブルに対するスモークチェックを実行します。この直近の期間内のリリースは、すべて検証済みのアップグレード元です。マイグレーションのフォルダーは、コミット後は変更できません。名前の変更、番号の付け替え、削除、変更のいずれかが行われるとリントが失敗し、ビルドも失敗します。いずれも、古い行を記録済みのデータベースでmigrate deployを壊してしまうためです。

1つのマイグレーションが失敗すると、それ以降のすべてのデプロイが止まります。migrate deployは最初の失敗で停止し、以降のマイグレーションをすべてP3009で拒否します。そのため、誰かが手作業で解消するまで、インストールは古いリリースのままになります。このプロジェクト自身の本番環境も、丸1日にわたってこの方法ですべてのデプロイを拒否され続けました。解消するには、失敗したマイグレーションが本当にロールバックされたことを確認する必要があります。作成されるはずのオブジェクトが1つも存在せず、変更されるはずだったものが元の形のままであることを確かめ、その後でロールバック済みとして解決してください。途中まで適用されたマイグレーションは、この対象にはなりません。

セルフホストのComposeデプロイに対するバックアップと復元は、リポジトリに記載されていません。そのため、このページでは手順を説明していません。

1台のマシン、またはマネージドクラウド

上記の単一ホストのDocker Composeでの構成が、実績のある方法です。このプロジェクト自身の本番環境も、この構成で動いています。単一のサーバー上で、データベース、オブジェクトストレージ、API、ワーカー、管理画面、サイトをそれぞれコンテナとして、共有のリバースプロキシの背後に置いています。

1台を超えてスケールすると、3つの点が変わります。これらはすべて、助言ではなく強制されます。localモード以外では、NessieはAPIとワーカーを別々のプロセスとして、それぞれ複数のコピーで実行します。そのため、マシンのディスクを1つのプロセスが所有していることを前提とする機能は、すべて拒否されます。具体的には、ファイルシステムのオブジェクトストレージ(起動失敗)、dockerプロバイダーを使うエグゼキューターテンプレート(無効で、プロビジョニングは明確に失敗します)、そしてfile_read、file_write、file_globの組み込みツール(エージェントが読める失敗したツール結果)です。どの拒否も、該当する設定、モード、代わりに使うべきものを明示します。

マネージドクラウドについては、リポジトリにGoogle Cloudを対象としたTerraformのツリーがinfrastructure/terraform/にあります。構成は次のとおりです。APIはCloud Runサービスとして、最低1インスタンスで動作します。APIは永続的なPostgresのLISTENクライアントを保持し、保守用のスイープを実行するため、ゼロにスケールするとリアルタイム配信が止まります。ワーカーはCloud Runのワーカープールとして動作します。ワーカーはポートをバインドしないため、サービスとして動かすとreadyになりません。すべてのロールアウトを制御するマイグレーションジョブがあります。データベースはPostgreSQL 17のCloud SQL、ストレージはS3互換バックエンド経由で到達するGCSバケットです。

そのパスは、書かれてはいるものの、実際には検証されていません。リポジトリはこの点を率直に述べており、このページも同様です。そのためのGoogle Cloudプロジェクトは存在せず、ツリーの内容は実際のプロジェクトに対して計画も適用もされていません。GCSへのマルチパートアップロードのパスは、実際のバケットでテストされていません。ワーカープールのリソースタイプも検証されていません。また、そこに記載された信頼できるプロキシのホップ数は計測値ではなく、計測すべき仮の値です。管理画面と公開サイトは、そこではまったくモデル化されていません。これは、検証すべき出発点として扱ってください。サポート対象のデプロイではありません。

デプロイのデータがどこにあるか、ソフトウェアが何を保持し、何を意図的に保持しないかについては、別のページで説明しています。EU製と、データの保存場所