メインコンテンツまでスキップ

プライベートなリモートデプロイ

この構成では、アプリケーションや Ollama のポートを公開せず、Libre WebUI、Ollama、Cloudflare Tunnel を 1 台の Docker ホスト上で実行します。Cloudflare Access が外側の ID 境界、Libre WebUI の認証が内側の境界です。Work と Watchtower は別個に有効化する、root 相当のオプションです。

このテンプレートは、単一レプリカの solo トポロジーです。SQLite、ローカルの暗号化 Blob、組み込みベクトル、ローカル連携、組み込みジョブワーカーがアプリケーションデータボリュームを共有します。.env でバックエンドセレクターを変更して、チーム向けデプロイに転用しないでください。チーム向けデプロイでは、リポジトリの docker-compose.team.yml(Work を有効にする場合は docker-compose.team.work.yml も)を使用する必要があります。この構成は PostgreSQL/PGVector、バージョン管理された S3 ストレージ、Redis、外部ワーカー、ゲートウェイを 1 つの連携トポロジーとしてプロビジョニングします。

出発点には deploy/private/docker-compose.yml を使用してください。デフォルトは main イメージです。

LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main

dev タグは、明示的に利用を選択した開発用インスタンスには適していますが、クライアントのデフォルトには適しません。

セキュリティモデル

  • Cloudflare Access は、/api/* と WebSocket のアップグレードを含め、ホスト名全体を保護します。公開バイパス経路を追加しないでください。
  • Libre WebUI のアプリケーション API には有効なアカウントが必要です。モデルのライフサイクル操作と Work の操作には、現在のデータベースロールが管理者である必要があります。
  • アプリケーション、Ollama、SearXNG、cloudflared は、プライベートな Compose ネットワークだけを使用します。ホストはアプリケーションポートを公開しません。
  • 同梱の SearXNG サービスは、任意のウェブ検索を提供します。内部専用で、管理者が設定 > 検索から検索を有効にするまで動作しません。構成を開始する前に、.envSEARXNG_SECRET を設定してください。
  • アプリは非 root で動作し、ルートファイルシステムは読み取り専用です。Linux capability はなく、no-new-privileges、CPU、メモリ、PID の上限も適用されます。
  • Work は、いずれかの上書き構成を含めない限り無効です。有効にした場合、そのコンテナにも読み取り専用ルートファイルシステム、capability の削除、リソース上限、ワークスペースボリューム、デフォルト拒否のネットワークポリシーが追加されます。

基本構成は Docker ソケットをマウントしません。docker-compose.work-proxy.yml で Work を有効にした場合も同様です。内部ネットワーク上のソケットプロキシがソケットを保持し、Work が使う API セクション(containers、images、volumes、networks、exec、info)だけを転送します。swarm、secrets、build、system のエンドポイントはプロキシで拒否され、アプリケーションにはソケットのマウントもソケットグループへの所属も不要です。プロキシは Docker API の範囲を狭めますが、転送した操作が及ぼす範囲までは狭めません。コンテナを作成できる主体はホストパスをバインドマウントできるため、これは実質的な堅牢化レイヤーとして扱い、マルチテナント分離とは考えないでください。

生のソケットを使う代替構成は、依然として最大の信頼境界です。docker-compose.work.yml と Watchtower の上書きは、任意の Docker API 呼び出しを発行できるプロセスをコンテナへ与え、ホストを制御できるようにします。ソケットを読み取り専用でマウントしても、Docker API アクセスが読み取り専用にはなりません。統合バックアップヘルパーは、生の Docker ソケットの継承を拒否します。スケジュールされた統合バックアップに依存する前に、Work をフィルタリング済みプロキシへ移行してください。

初期構築

  1. 非 root の sudo 運用ユーザーを作成し、root SSH を無効化する前に、鍵認証による SSH ログインを確認します。
  2. deploy/private/.env.example/opt/libre-webui/.env へコピーし、モードを 0600 に設定して一意のシークレットを生成し、ホストに合わせて BLOB_QUOTA_BYTES_PER_USER を設定します。BLOB_QUOTA_RESERVATION_TTL_MS は放置されたアップロード予約を失効させます。デフォルトは 1 時間です。
  3. Work を有効にする場合は、DOCKER_GID/var/run/docker.sock を所有するグループの数値 ID に設定します。
  4. Cloudflare のトンネルトークンを /opt/libre-webui/secrets/tunnel-token に保存し、モードを 0640 以下の権限に設定します。
  5. ホスト名全体を対象とする Cloudflare Access のセルフホスト型アプリケーションを作成し、24 時間のセッションを使用して、必要な ID だけを許可します。トンネル経路で Protect with Access を有効にします。監視に公開ヘルスチェックが必要な場合は、/health/live だけを対象に、別のパス限定アプリケーションまたはポリシーを作成します。メインアプリケーションへ包括的な Bypass ポリシーを追加しないでください。一致する Bypass ポリシーは Allow ポリシーを無効化します。
  6. ENABLE_SIGNUP=false のままにします。Access の許可リストがホスト名を保護した後、最初のローカル管理者を作成します。空のデータベースでは、その 1 つの初期アカウントが自動的に許可されます。後で意図的に登録期間を設ける場合だけ、登録を有効にしてください。
  7. Turnstile のホスト名制限を設定し、TURNSTILE_EXPECTED_HOSTNAME を正確な公開ホスト名に設定します。

起動して確認します。

cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps

Work を有効にするには、意図的にソケットプロキシの上書きを含めます。

docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d

生のソケットを使う構成(docker-compose.work.yml)も、必要とするデプロイ向けに残っています。信頼上の影響は上記のとおりです。

Access が有効になると、コマンドラインのスモークテストには、その正確なパスに限定的なバイパスがない限り、Cloudflare Access のサービストークンが必要です。資格情報をシェル履歴の外に保存し、両方のヘッダーを送信します。

curl --fail --silent --show-error \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
https://your-hostname.example/api/auth/system-info

保護されたアプリケーション API への未認証リクエストは、401 を返す必要があります。

curl --output /dev/null --write-out '%{http_code}\n' \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
https://your-hostname.example/api/work/tasks

ホストの堅牢化

このディレクトリには sshd の drop-in と fail2ban jail が含まれています。sshd の drop-in を適用する前に、別のターミナルで非 root の sudo セッションを確認してください。SSH を再読み込みする前に、sshd -t で設定をテストします。

UFW(または同等のファイアウォール)を使い、受信トラフィックをデフォルト拒否し、レート制限付きの SSH だけを許可します。このテンプレートでは、Docker はサービスポートを一切公開しません。

ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable

無人のセキュリティアップグレードを有効なままにしてください。デプロイ上の必要性が文書化されていない限り、X11、エージェント、TCP の転送を無効にします。

バックアップと復旧

バックアップを作成する前に、実行中のデプロイコンテナ内で読み取り専用の復旧インベントリを実行します。これにより、実際にデプロイされているアプリケーションのバージョン、環境、マウント済みデータボリュームを正確に使用できます。ホスト上のチェックアウトからコマンドを実行すると、誤ったデータベースを調査したり、デプロイ済みイメージとは異なるソースを実行したりする可能性があります。

docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data

終了ステータス 0 は復旧準備の阻害要因がないこと、1 は JSON レポートに阻害要因があること、2 はコマンドを実行できなかったことを示します。レポートに含まれるのは、暗号化鍵のフィンガープリントとシークレットの存在フラグだけで、鍵やその他のシークレット値は決して表示しません。対応するバックアップと一緒にインベントリを保管してください。運用担当者は復元前に、アプリケーションバージョン、スキーマのフィンガープリント、想定される Work リソース、除外項目を比較できます。

実際にデプロイされているイメージを使い、専用のバックアップ暗号化鍵と署名鍵を作成します。このディレクトリはアプリケーションボリューム外に置き、暗号化鍵と署名用秘密鍵を、別の保護された復旧場所へコピーします。

install -d -m 0700 /etc/libre-webui/backup-keys
image_ref=$(docker inspect libre-webui --format '{{.Image}}')
docker run --rm --user 0:0 --read-only --network none --cap-drop ALL \
--security-opt no-new-privileges \
--mount type=bind,src=/etc/libre-webui/backup-keys,dst=/backup-keys \
--entrypoint /usr/local/bin/libre-webui "$image_ref" \
backup keygen \
--directory /backup-keys

鍵生成は、出力ファイルがすでに存在する場合は拒否されます。既存のバックアップセットへ新しい鍵を上書き生成しないでください。アーカイブ暗号化鍵または署名 ID のどちらかを失うと、対応する復旧証明を利用できなくなります。

付属のバックアップ/復元スクリプトと systemd ユニットをインストールし、タイマーを有効にします。

install -d -m 0700 /var/backups/libre-webui
install -m 0750 deploy/private/libre-webui-backup \
/usr/local/sbin/libre-webui-backup
install -m 0750 deploy/private/libre-webui-restore \
/usr/local/sbin/libre-webui-restore
install -m 0644 deploy/private/libre-webui-backup.{service,timer} \
/etc/systemd/system/
systemctl daemon-reload
systemctl enable --now libre-webui-backup.timer

ユニットは、任意のメンテナンス専用上書きを /etc/libre-webui/backup.env から読み取ります。アプリケーションの .env は読み込みません。上書きが必要な場合だけ、root としてファイルを作成します。

install -d -m 0750 /etc/libre-webui
install -m 0600 /dev/null /etc/libre-webui/backup.env

LIBRE_WEBUI_STACK_DIRLIBRE_WEBUI_BACKUP_RETENTION_DAYSLIBRE_WEBUI_CONTAINER_NAMELIBRE_WEBUI_BACKUP_KEY_DIR は、このファイルで直接設定できます。所有者を root、モードを 0600 に維持してください。カスタム鍵ディレクトリは、systemd サンドボックス内の root から読み取り可能でなければなりません。

LIBRE_WEBUI_BACKUP_DIR を変更すると、systemd の書き込み境界も変わります。サービス起動前にディレクトリが存在し、ユニットに一致する drop-in が必要です。たとえば、backup.envLIBRE_WEBUI_BACKUP_DIR=/srv/backups/libre-webui を設定した後、次を実行します。

install -d -m 0700 /srv/backups/libre-webui
systemctl edit libre-webui-backup.service

エディターで次の正確なパスを追加し、ユニットを再読み込みします。

[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service

一致する ReadWritePaths= 項目がなければ、ProtectSystem=strict により、タイマーからカスタム場所への書き込みが正しく禁止されます。

バックアップサービスは、大きなアーカイブ向けに最大 6 時間を許可します。ヘルパーはホストロックを取得し、アプリケーションがすでに実行中だった場合だけ停止し、実際にデプロイされたイメージを使って、静止状態のボリュームからアーカイブを作成します。アーカイブには署名付きマニフェストと、運用担当者の鍵で暗号化されたペイロードがあります。データディレクトリに加え、その状態を開くために必要なランタイム設定とシークレット設定が含まれます。ヘルパーは完全なアーカイブを独立して検証してから、メタデータレポートをアトミックに公開します。読み取り専用のメンテナンスコンテナには、SQLite の検査と認証済みアーカイブ検証用として、プライベートで書き込み可能な /tmp tmpfs が与えられます。一時的な平文がコンテナレイヤーへ永続化されることはありません。両方のファイルと、別に保護した復旧鍵をホスト外へコピーしてください。

Work が docker-compose.work-proxy.yml を使用する場合、復旧ではデータベースから参照されるすべての Work ボリュームがまだ存在することも証明する必要があります。ヘルパーは、デプロイ済みアプリケーションの DOCKER_HOST を読み取り、同じ稼働中 Compose プロジェクト内のソケットプロキシサービスを見つけ、Docker の実際のネットワーク接続から両者が共有する 1 つの内部ネットワークを検出します。Compose はプロジェクト名をそのネットワーク名の先頭に付けるため、推測したネットワーク名を設定したり固定したりしないでください。アーカイブ作成コンテナだけがその内部ネットワークに参加し、フィルタリング済みプロキシへ到達できます。生のソケットは与えられません。独立したアーカイブ検証は --network none で続行します。プロキシがない、想定外のエンドポイントである、共有ネットワークが外部または曖昧である、生のソケットがマウントされている、といった場合は、アプリケーションを停止する前、アーカイブを公開する前に失敗します。

稼働中ボリュームを置き換えず、新しいボリュームへ復旧するテストを行います。

LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill

復元ヘルパーは、既存のボリュームまたは設定先への復元を拒否します。アーカイブと内部の復旧インベントリを使い捨てストレージで検証した後、新しいボリュームへデータをコピーし、復旧した runtime.jsonsecrets.json を非公開権限で書き込みます。稼働中の構成を書き換えたり起動したりすることはありません。復旧した設定を確認し、デプロイ固有の値を意図的に更新して、分離した構成で復元ボリュームをテストしてください。

Ollama モデルは再取得できます。Docker の Work ボリューム、Kubernetes の Work PVC、ホストに紐付いた Work フォルダーはアプリケーションデータディレクトリの外にあり、独自の連携スナップショットと保持ポリシーが必要です。

更新

イメージタグが可変でも、Libre WebUI は状態を持ちます。基本 Compose ファイルには、アプリケーションを Watchtower の対象外とするラベルが恒久的に付いています。連携された運用担当者の操作としてだけアップグレードしてください。

  1. 実行中のイメージ ID を記録し、レビュー済みの置き換え先を不変ダイジェストとして解決します。
  2. libre-webui recovery-check を実行してバックアップサービスを開始し、続行前に新しいアーカイブと検証レポートが作成されていることを必須とします。
  3. LIBRE_WEBUI_IMAGE をレビュー済みダイジェストに設定し、取得して、Docker Compose で libre-webui だけを再作成します。データボリュームを削除または再作成しないでください。
  4. /health/ready、サインイン、セッション/履歴、ドキュメント検索、Work のスモークテストが成功することを必須とします。失敗した場合は記録済みイメージダイジェストへロールバックし、診断用に失敗時の状態と検証済みバックアップの両方を保存してください。

ホスト側の手順は意図的に手動です。レビュー後にだけダイジェストを置き換え、取得前に最新の .lwb.json の組を確認します。

docker inspect libre-webui --format '{{.Config.Image}} {{.Image}}'
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
systemctl start libre-webui-backup.service
systemctl --no-pager --full status libre-webui-backup.service
ls -lt /var/backups/libre-webui/libre-webui-integrated-* | head

# Set LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui@sha256:REVIEWED_DIGEST
# in the root-owned .env, then recreate only the application.
docker compose pull libre-webui
docker compose up -d --no-deps libre-webui
docker inspect libre-webui --format '{{.State.Health.Status}} {{.Image}}'

ソケットを保持する任意の Watchtower 上書きは、基本ファイルで明示的にラベル付けされたサイドカーだけを対象に、引き続き利用できます。

docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d

Watchtower は Ollama と SearXNG を 30 分ごとに確認します。Ollama のモデルデータは名前付きボリュームに、SearXNG の設定はバインドマウントに残ります。Libre WebUI、cloudflared、Work ソケットプロキシ、Work サンドボックスは更新しません。クライアント用デプロイは main に従い、実験用インスタンスは :dev を選択できますが、アプリケーションには同じくバックアップを前提とする手動アップグレードが必要です。このプライベートな solo 構成をチーム用の永続化サービスへ接続しないでください。代わりに完全なチームトポロジーをデプロイしてください。