これは 1 台のホストで LMS、バックグラウンド処理、リアルタイムサービス、共同編集、プロファイルで有効化する各種サービスを動かす完全な製品です。設計は docs/architecture/23-ops.md のセクション 2 を参照してください。
その他の構成: Vercel と Cloudflare Workers では Web 層だけを動かします。アップグレードは upgrade.md、バックアップと復元ドリルは backup-restore.md を参照してください。
必要なもの
- Compose プラグイン 2.30 以降を含む Docker Engine 27 以降。
- 既定のスタックには CPU 4 コアとメモリー 8 GB。
--profile fullでは 8 コア、16 GB (ClamAV だけで約 1.5 GB の署名データを保持します)。 - Web 層用 DNS 名と、信頼できないコンテンツ用の別の DNS 名。ホスト名を分けてください。SCORM パッケージとアップロードした HTML はコンテンツオリジンで実行し、LMS の Cookie を読めないようにします。
- ローカルテストでは
lvh.meと*.localhostが 127.0.0.1 に解決されます。これはdocker/.env.exampleが使う設定です。スタックのproxyサービスがローカル認証局を使って両方に https を提供するため、追加ソフトウェアは不要です (「TLS」を参照)。 - ホストでポート 80 と 443 が空いていること (
QUIRE_PROXY_HTTP_PORT、QUIRE_PROXY_HTTPS_PORTで変更できます)。
初回起動
QUIRE_APP_ORIGIN=https://learn.example.org \
QUIRE_CONTENT_ORIGIN=https://content.example-content.org \
QUIRE_SETUP_ADMIN_EMAIL=you@example.org \
docker/scripts/init-env.sh
docker compose -f docker/compose.yaml up -d --build
docker compose -f docker/compose.yaml logs initdocker/scripts/init-env.sh は docker/.env を docker/.env.example から を作成し、データベースのパスワード、署名鍵とマスターキー、コンテンツ起動用鍵ペアなどのシークレットを生成します。また、監査チェックポイント署名鍵を docker/secrets/audit-signing-key.pem に作成します。Compose はこれをシークレットとして worker にマウントします。必要なのは sh、awk、openssl だけです。既存の docker/.env は上書きしません。両方のファイルをホスト外にコピーしてください。QUIRE_MASTER_KEY がないと、復元した DB 内の保存済み認証情報を復号できません。手動でファイルを作る場合は cp docker/.env.example docker/.env を実行し、ファイル内の説明に従って各シークレットを生成します。
両方のオリジンは https を使い、登録可能ドメインも異なる必要があります。本番環境ではコンテンツサービスが平文 http を拒否します。proxy サービスが両方の TLS を終端します (「TLS」を参照)。init-env.sh は http:// のオリジンを拒否します。
スタックは決められた順番で起動し、各段階で前の段階を待ちます。
postgresが正常状態になります。初回起動時に init スクリプト (docker/postgres/init/90-passwords.sh) が 4 つのロールのパスワードを設定します。migrateがすべてのマイグレーションを適用し、制御 DB と専用テナント DB すべてにジョブキューを初期設定して、一致を確認した後に終了します (docs/ops/upgrade.md)。起動のたびにマイグレーションが動き、冪等です。アップグレードでは新しいイメージを使い、再起動します。init(apps/web/src/first-run.ts) がQUIRE_DATABASE_IDのアプリケーション DB を記録します。QUIRE_SETUP_ADMIN_EMAILを設定した場合、最初の組織と管理者を作成します。サインインアドレスと生成したパスワードはdocker compose logs initに一度だけ表示されます。web、content、worker、scheduler、collab、centrifugoが起動します。proxyはwebとcontentが正常状態になってから起動します。
https://demo. にアプリケーションドメインを続けたアドレスを開き、サインインします (init ログに正確なアドレスが表示されます)。ローカル環境では最初に proxy の認証局を信頼してください (「TLS」を参照)。生成したパスワードは /account/security で変更します。
必須シークレットがないプロセスは起動を拒否し、ログに不足している設定名を表示します。不完全な状態で起動することはありません。
サービスとプロファイル
| サービス | プロファイル | 役割 |
|---|---|---|
| postgres | 常時 | データベース (PostgreSQL 18、pgvector 入り。docker/postgres.Dockerfile で構築)。初回起動から WAL をアーカイブ |
| migrate、init | 常時 | 1 回実行: マイグレーション、次に初回設定 |
| web | 常時 | LMS。QUIRE_HTTP_PORT (8080) で待ち受け |
| content | 常時 | 信頼されないコンテンツのオリジン。QUIRE_CONTENT_PORT (8081) で待ち受け |
| worker | 常時 | メール、レポート、ファイル処理、webhook などのバックグラウンドジョブ |
| scheduler | 常時 | 64 個の実行時スケジュールを登録して worker へ渡す。リーダーは常に 1 つ |
| collab | 常時 | 共同編集 websocket。QUIRE_COLLAB_HTTP_PORT (1234) で待ち受け |
| centrifugo | 常時 | リアルタイム配信。QUIRE_REALTIME_PORT (8000) で待ち受け |
| proxy | 常時 | Caddy。ポート 80 と 443 の TLS 入口 (「TLS」を参照) |
| valkey | cache |
キャッシュとレート制限 |
| clamav | scan |
アップロードのマルウェアスキャン |
| gotenberg | preview |
Office 文書の PDF プレビューと証明書の描画 |
| imgproxy | images |
画像サイズの変更と形式変換 |
| transcoder | video |
LGPL の ffmpeg だけを含む worker イメージ。動画バージョンを生成 |
| seaweedfs | storage |
このホスト上の S3 互換オブジェクトストレージ |
| otelcol | observability |
OpenTelemetry コレクター |
| mailpit | devmail |
動作確認用に送信メールをすべて受信 |
| backup | backup |
1 回実行のベースバックアップ。を参照 |
| backup-scheduler、backup-offsite | backup |
QUIRE_BACKUP_INTERVAL_HOURS ごとにベースバックアップを作成し、暗号化したホスト外コピーと週次検証ドリルを実行 |
| h5p | h5p |
用意した QUIRE_H5P_IMAGE の H5P LTI 1.3 ツールイメージ。QUIRE_H5P_PORT (8090) で待ち受け。「H5P プロバイダーの接続」を参照 |
--profile full は backup と h5p 以外のすべての任意サービスを起動します。個別サービスは docker compose -f docker/compose.yaml --profile scan up -d のように起動します。任意サービスがなくても Quire は動きますが、不足内容を知らせます。スキャナーがないとアップロードはスキャンなしで保存され、管理者に通知されます。Gotenberg がないとプレビューの代わりにダウンロードを提供します。トランスコーダーがないと動画は元ファイルのまま再生されます。
サードパーティの各イメージとライセンス上の義務は docker/third-party-containers.yaml に記載されています。
H5P プロバイダーの接続
Quire は H5P の実行環境やサイドカーを埋め込み・同梱しません (ADR 0019)。H5P を使う場合は、Quire と別にホスト型サブスクリプションを用意するか、セルフホストの H5P インスタンスを運用してください。そのプロバイダーを LTI 1.3 外部ツールとして登録し、コースにツールアクティビティとしてコンテンツを追加します。Quire は LTI Assignment and Grade Services (AGS) 経由で成績とアクティビティ・採点の進捗を交換します。プロバイダーが xAPI ステートメントも送信する場合は、Quire の xAPI ステートメントストア向けに別途設定してください。AGS の成績・進捗交換は xAPI ステートメントを送りません。Moodle からのインポートでは H5P アクティビティに LTI ツール接続が必要と表示されます。H5P 実行環境、作成機能、コンテンツバンク、受験履歴の管理はプロバイダー側です。
同じホストでセルフホストのインスタンスを動かすには、QUIRE_H5P_IMAGE にそのイメージを設定して h5p プロファイルを起動します。Compose は QUIRE_H5P_PORT (8090) で公開し、h5p-data ボリュームにデータを保存します。イメージとそれに伴う義務は利用者側のものです。
設定
すべてのプロセスは docker/.env を読み込みます。設定の既定値はテンプレート docker/.env.example に記載されています。設定は次のグループに分かれます。
アドレス
| 設定 | 意味 |
|---|---|
QUIRE_APP_ORIGIN |
LMS の公開アドレス。例: https://learn.example.com |
QUIRE_CONTENT_ORIGIN |
別のホストにあるコンテンツオリジン |
QUIRE_PLATFORM_DOMAINS |
組織が属するドメイン。コンマ区切り |
QUIRE_MARKETING_ORIGIN |
Optional. The marketing site, default https://quirelms.com. The only origin the waitlist form (POST /api/waitlist, POST /waitlist) accepts and redirects to. Comma separated; a www. variant is allowed only if listed |
QUIRE_DEPLOY_TARGET |
この環境では compose。vercel と cloudflare は各ガイドを参照 |
QUIRE_TRUSTED_PROXY_CIDRS |
X-Forwarded-For を信頼するプロキシ |
シークレット
| 設定 | 意味 |
|---|---|
QUIRE_SECRET_KEY |
セッションとトークンに署名。16 進数 64 文字 |
QUIRE_MASTER_KEY |
SSO や webhook のシークレットなど、保存済み認証情報をラップ。32 バイト、base64。Web 層と worker で同じ値が必要。ローテーションは key-rotation.md を参照 |
QUIRE_MASTER_KEY_VERSION |
マスターキーのバージョンラベル。未設定時は v1。ローテーション時に更新 |
QUIRE_MASTER_KEY_RETIRED |
暗号化済みデータを読むために必要な旧マスターキー。v1=<base64> 形式。未解決なしでローテーションが終わったら削除 |
QUIRE_COLLAB_SIGNING_KEY |
Web と collab で共有し、編集トークンに署名 |
QUIRE_BACKUP_SIGNING_KEY |
コースバックアップに署名 (任意) |
QUIRE_MASTER_KEY のコピーをホスト外に保管します。鍵なしで復元した DB は、保存済み認証情報を復号できません。
データベース
| 設定 | 意味 |
|---|---|
POSTGRES_PASSWORD |
コンテナとバックアップが使う superuser |
QUIRE_DB_APP_PASSWORD、QUIRE_DB_MIGRATOR_PASSWORD、QUIRE_DB_REPORT_PASSWORD、QUIRE_DB_AUDIT_PASSWORD |
初回起動時に設定する各ロールのパスワード |
DATABASE_URL |
アプリケーションロール。各クエリに行レベルセキュリティが適用されます |
DATABASE_MIGRATOR_URL、QUIRE_MIGRATION_URL |
migrate と init が使う migrator ロール |
QUIRE_SUPERUSER_URL |
初回起動時のみ使用 |
QUIRE_REPORT_DATABASE_URL |
レポートとレポートビルダー用の読み取り専用ロール |
QUIRE_AUDIT_DATABASE_URL |
監査コンソールと SIEM エクスポート用の監査ロール |
QUIRE_DATABASE_ID |
任意の UUID。インストール中は固定 |
ロールのパスワードを設定するのは、最初にデータベースボリュームを作るときだけです。後で変更するには ALTER ROLE を使い、対応する URL も更新してください。
QUIRE_REPORT_DATABASE_URL は DATABASE_URL が示す物理 DB で使われます。他の登録済み物理 DB では、その DB 専用の quire_report 接続 URL を Web と worker の環境に設定し、変数名を DB の Reporting environment variable 欄に env:NAME として登録します。参照先はアプリ接続先と同じ DB (できれば読み取りレプリカ) である必要があります。レポートビルダー、保存済みレポート、定期配信、エクスポート、分析、監査ログ、REST 監査リソース、アシスタントの監査検索を含むレポート機能は、そのテナント専用のレポート接続を使います。別 DB の URL を借りることはありません。レポート接続のない DB では通常のレポートは独自のアプリケーション接続で実行されますが、アプリケーションロールでは監査記録を読めないため、分析と監査の読み取りは拒否されます。
ドライバー
| 設定 | このリリースの値 | 注記 |
|---|---|---|
QUIRE_STORAGE_DRIVER |
local (既定)、s3、azure |
local はファイルを files ボリュームに保存します。s3 は AWS S3、R2、GCS との相互運用、他の S3 互換ストアに対応し、再開可能なマルチパートアップロードを使います |
QUIRE_REALTIME_DRIVER |
inprocess (既定)、sse、centrifugo、durable_objects |
Web コンテナが 1 つなら inprocess。複数なら centrifugo または sse を使います |
QUIRE_CACHE_DRIVER |
memory (既定)、postgres、valkey |
memory はプロセスごとです。複数コンテナ間で上限を維持するには valkey または postgres を使います |
QUIRE_VIDEO_DRIVER |
ffmpeg (既定)、progressive_mp4 |
Cloudflare Stream、Mux、Bunny のホスト型プロバイダーも鍵で設定できます |
QUIRE_IMAGE_DRIVER |
noop (既定)、imgproxy、cloudflare |
noop は元サイズで画像を配信します。imgproxy には images プロファイルと下記設定が必要です。cloudflare は Cloudflare Images を使います |
QUIRE_MEETING_PROVIDER |
bbb、zoom、teams、meet、jitsi、in_process |
ライブセッション向けのプラットフォーム既定プロバイダーです。未設定では組織が Integrations、Live session provider で独自アカウントを接続するまで、ライブセッションは未設定と表示されます。組織独自のアカウントが常にこの値より優先されます。各プロバイダーの設定 (BBB_URL と BBB_SECRET、ZOOM_*、TEAMS_*、GOOGLE_MEET_*、JITSI_*) は、ここで指定したプロバイダーのものだけ読み込みます |
QUIRE_MEETING_REGIONS |
eu、uk、us のコンマ区切り |
プラットフォーム既定プロバイダーが会議を処理するリージョンです。未設定の場合、リージョンに固定された組織との照合は行いません。組織独自アカウントでは設定ページにリージョンを記載します |
このリリースで提供されないドライバー値は、既定値に黙って置き換えず、Web 層の起動時に設定名を示して拒否します。
画像
ページは /api/files/{id}/image/{size} 経由で 4 種類の固定サイズの画像を要求します。ファイルと同じアクセス権を確認してから画像サービスへリダイレクトします。各組織は 1 時間あたり QUIRE_IMAGE_SPECS_PER_HOUR (既定値 2000) 組まで新しい画像とサイズを要求できます。その時間内に生成済みのサイズは数えません。Web コンテナが複数ある場合、複数の Web コンテナでは上限を維持するため valkey または postgres を QUIRE_CACHE_DRIVER に設定します。
| 設定 | ドライバー | 注記 |
|---|---|---|
IMGPROXY_URL |
imgproxy |
ブラウザーからアクセスできる imgproxy のアドレス。例: https://images.example.org。プロファイル images は QUIRE_IMAGES_PORT (8082) に公開します |
IMGPROXY_KEY、IMGPROXY_SALT |
imgproxy |
imgproxy の起動時と同じ 16 進数文字列。各値を openssl rand -hex 32 で生成します。Quire がすべての画像 URL に署名するため、Quire が要求していない画像を imgproxy が描画することはありません |
QUIRE_IMAGE_SOURCE_ORIGIN |
ローカルストレージでの imgproxy |
imgproxy が元画像を取得する場所。Compose は http://web:3000 を設定します。s3 や azure のストレージではバケットから取得するため使いません |
CLOUDFLARE_ACCOUNT_ID、CLOUDFLARE_IMAGES_TOKEN、CLOUDFLARE_IMAGES_ACCOUNT_HASH |
cloudflare |
Images の編集権限を持つ API token と、Images の Developer resources にある account hash。アカウントの flexible variants を有効にします |
CLOUDFLARE_IMAGES_SIGNING_KEY |
cloudflare |
任意。設定すると画像は非公開になり、各 URL に署名と有効期限が付きます。未設定の場合、画像は公開され、QUIRE_SECRET_KEY から派生した推測できない URL を使います |
Cloudflare Images は配信した元画像のコピーを保持します。ファイルが削除されると worker が元ファイルより先にコピーを削除します。
キュー
バックグラウンドジョブは同じ Postgres にある pg-boss を使うため、キューサービスの起動や設定は不要です。ジョブは原因となった変更と同じトランザクション内で登録されるので、クラッシュで消失したり二重送信されたりしません。この環境での QUIRE_QUEUE_DRIVER の既定値は pgboss です。vercel と cloudflare は軽量な通知と webhook 配信だけを各プラットフォームのキューに移します。登録方法は Vercel と Cloudflare のガイドを参照してください。
メール
次のどちらかを設定します。
QUIRE_EMAIL_PROVIDER_CONFIG: HTTP プロバイダーと認証情報を指定する JSON オブジェクト。例:{"provider":"postmark","token":"..."}。Postmark、Amazon SES、Mailgun、SendGrid、Resend に対応します。QUIRE_SMTP_URL:smtp://user:password@host:587。この構成だけで使えます。サーバーレス環境では SMTP をブロックします。
QUIRE_MAIL_FROM は送信者です。動作確認では devmail プロファイルを起動し、QUIRE_SMTP_URL=smtp://mailpit:1025 に設定して http://localhost:8025 でメールを確認します。
任意のサービス
| 設定 | プロファイル |
|---|---|
CLAMAV_URL=tcp://clamav:3310 |
scan |
GOTENBERG_URL=http://gotenberg:3000 |
preview |
IMGPROXY_KEY、IMGPROXY_SALT |
images |
VALKEY_URL=redis://valkey:6379 |
cache |
QUIRE_OPENSEARCH_URL または QUIRE_MEILISEARCH_URL |
外部検索。未設定なら Postgres 全文検索 |
QUIRE_BREACH_CHECK_PROVIDER=off、QUIRE_BREACH_CHECK_URL |
パスワード漏えい検査。既定では api.pwnedpasswords.com に対して有効 (ハッシュ先頭 5 文字だけ送信)。off で無効化し、URL には自分でホストする range API を指定 |
可観測性
OTEL_EXPORTER_OTLP_ENDPOINT に各プロセスがトレースとメトリクスを送るコレクターを指定します。observability プロファイルでは http://otelcol:4318 です。バックエンドへの exporter は docker/otel-collector.yaml に追加します。Web 層、worker、scheduler、content、collab は、この設定があれば OTLP/HTTP でスパン (Web リクエスト、テナント DB トランザクション、worker ジョブ、外部への呼び出し) と毎分のメトリクスを同じエンドポイントへ送信します。OTEL_METRICS_EXPORTER=none でメトリクスを無効にできます。OTEL_TRACES_SAMPLER_ARG は保持するトレースの割合です。ログは LOG_LEVEL に従って標準出力へ書き、Compose がローテーションします。トレースには個人データが入りません。
EU データ所在地のリージョン別送信先
QUIRE_REGION=eu は EU 組織をこのスタックで提供することを示します。worker は EU に固定された組織から外部へ送る各リクエストを許可リストで制限します (21-compliance.md セクション 8.1)。許可リストには設定済みサービスがリージョン用に申告するホスト (ストレージエンドポイント、メール、ホスト型動画、組織独自のストレージ、AI プロバイダー、メールアカウント)、有効な例外対象サービスのホスト、QUIRE_EGRESS_ALLOW_HOSTS に記載したホストが含まれます。他の公開ホストへのリクエストは送信前に拒否され、拒否内容は組織の監査ログに privacy/egress_refused と記録され、Compliance、Data residency に表示されます。
| 設定 | 値 | 効果 |
|---|---|---|
QUIRE_EGRESS_ALLOW_HOSTS |
ホスト名のコンマ区切り、または全サブドメイン用の *.example.org |
EU 組織が追加で接続できるホスト。Webhook、xAPI、SIEM エンドポイント、ブログフィード、Amazon SES は組織が選ぶものでありサービス側の宣言がないため、ここに記載します。ループバック、プライベートアドレス、web や clamav のような単一ラベル名は自分のネットワークなので検査されません |
英国と米国の組織にはホスト一覧の制限を適用せず、サービスのリージョン確認を使います。リストは worker に設定します。管理画面は Web 層で読み込んで許可リストを表示するため、すべてのサービスが読む docker/.env に設定してください。
アプリケーションの確認は明確なエラーと監査エントリーを残しますが、それだけでは保証になりません。コードは誤る可能性があります。保証するのはネットワークです。Compose はネットワーク制限を自動で設定しません。リージョン用スタックでは worker と web を internal: true ネットワークに置き、唯一の外向き経路を egress proxy (Squid や tinyproxy コンテナなど) に限定します。プロキシは QUIRE_EGRESS_ALLOW_HOSTS と設定済みサービスのホストだけを許可し、各サービスの HTTPS_PROXY を設定します。レジデンシーページにアプリケーションの許可ホスト一覧が表示されるため、ネットワーク側と照合できます。
ヘルス
| エンドポイント | 意味 |
|---|---|
/healthz |
生存確認: プロセスが応答します。Compose のヘルスチェックが使います |
/readyz |
準備状態: 依存サービスへ接続でき、任意サービスごとに設定済みかどうかを報告します。ロードバランサーはこちらを確認します |
docker compose -f docker/compose.yaml ps で各サービスの状態を確認します。
TLS
proxy サービス (Caddy、Apache-2.0、docker/caddy/Caddyfile) は既定スタックに含まれ、ポート 80 と 443 で応答して次のようにルーティングします。
| ホストまたはパス | 転送先 |
|---|---|
QUIRE_PROXY_CONTENT_HOST |
content |
QUIRE_PROXY_APP_HOST、すべてのテナントサブドメインとカスタムドメイン |
web |
それらのホストの /_collab/ |
collab (websocket、QUIRE_COLLAB_URL) |
それらのホストの /_realtime/connection/ |
centrifugo のクライアント websocket。サーバー API は公開されません |
それらのホストの /_images/ |
imgproxy、images プロファイル (IMGPROXY_URL) |
init-env.sh は 2 つのオリジンから QUIRE_PROXY_APP_HOST、QUIRE_PROXY_CONTENT_HOST、QUIRE_PROXY_HTTPS_PORT、QUIRE_COLLAB_URL、IMGPROXY_URL を導出し、設定の不整合を防ぎます。オリジンを手動変更する場合は、関連値を一緒に変更してください。
証明書は QUIRE_PROXY_TLS で選びます。
-
internal(既定):localhost、*.localhost、lvh.me向けの Caddy 独自認証局です。ルート証明書を一度信頼設定に追加してから、次のように取得します。docker compose -f docker/compose.yaml cp \ proxy:/data/caddy/pki/authorities/local/root.crt ./quire-local-ca.crtquire-local-ca.crtを OS またはブラウザーの信頼ストアに追加します。curlでは--cacertを指定します。 -
メールアドレス: 実ホスト名用 ACME 証明書を自動発行します (Let’s Encrypt、次に ZeroSSL)。両オリジンとすべてのテナントホストの DNS をこのホストへ向け、ポート 80 と 443 をインターネットから到達可能にしてください。
テナント用ホストの証明書は初回アクセス時に必要に応じて発行します。ただし、Web が Compose ネットワーク経由の /tls-allowed で、その名前がこのインストールに属することを確認した場合だけです。ワイルドカード証明書や DNS プロバイダープラグインは不要で、無関係な人が自分のドメインをホストへ向けても証明書を要求できません。証明書とローカル認証局は caddy-data ボリュームに保存されます。internal を使う場合は他のデータと一緒にバックアップしてください。
Web は proxy からの X-Forwarded-For だけを信頼します。proxy は固定アドレス (QUIRE_PROXY_ADDRESS、既定値 172.29.64.10) を固定サブネット (QUIRE_COMPOSE_SUBNET) 上に持ち、QUIRE_TRUSTED_PROXY_CIDRS にそのアドレスを指定します。サブネットがホストのネットワークと重複する場合は両方を変更してから docker compose down、続けて up を実行します。
独自リバースプロキシの背後で運用
既存のロードバランサーやプロキシを使う場合は proxy を外し (docker compose up -d --scale proxy=0)、web (8080)、content (8081)、collab (1234、websocket)、centrifugo (8000、websocket) の前で TLS を終端します。公開アドレスを QUIRE_APP_ORIGIN、QUIRE_CONTENT_ORIGIN、QUIRE_COLLAB_URL (wss://) に、プロキシのアドレス範囲を QUIRE_TRUSTED_PROXY_CIDRS に設定します。
トラブルシューティング
initが “QUIRE_DATABASE_ID is not a UUID” で終了する場合は、uuidgenで値を設定します。webが “did not start on compose” で再起動する場合、ログに対応できない設定と代わりに使う値が表示されます。- 初回起動後に
.envでロールパスワードを変更しても反映されません。init スクリプトは 1 度しか実行しません。ALTER ROLEを使ってください。 CLAMAV_URLを設定中にスキャンエラーでアップロードが失敗する場合、ClamAV が初回起動時に署名をダウンロードしています。数分待ってください。