OpenWebUI + Portainer + Keycloak 構築ログ〜 GUI管理 + SSO化で「ひとり情シス運用」を一段楽にした話 〜

生成AIを社内で使い始めてから、ツール自体は便利になった。
でも、その裏で 新しい管理地獄 が静かに始まっていた。

ユーザー管理、権限管理、アカウント削除、パスワード忘れ対応。
正直、「これ、楽になるどころか増えてないか?」というのが本音だった。

前回、Dify から OpenWebUI に切り替えて、UI や操作感はかなり改善された。
ただ、そこで次にぶつかったのが ユーザー管理問題

OpenWebUI 単体だと、また個別にユーザーを作って管理する必要がある。
ツールが増えるたびにユーザー管理が増えていく構造は、ひとり情シスにはかなりきつい。

そこで今回、

  • Docker 管理を GUI 化するための Portainer
  • 認証統合のための Keycloak
  • 既存の LINE WORKS を IdP とした SAML 連携

この3点を組み合わせて、
OpenWebUI + SSO + GUI管理構成 を作ることにした。

なお、この構成に至るまでに、Dify を使った検証OpenWebUI への切り替え → 現在の構成、という流れを辿っています。


なぜ SSO をやろうと思ったか

正直、SSO は前から知っていた。
ただ「設定が面倒」「証明書」「メタデータ」みたいな単語を見るたびに、後回しにしてきた。

でも、

  • ツールが増える
  • ユーザー管理も増える
  • 削除漏れ、権限ミス、退職者アカウント問題が積み上がる

この流れが見えた時点で、「もう一度ちゃんとやろう」と腹を括った。

ちょうど社内では LINE WORKS を使っていて、
SAML2 に対応している。

Keycloak を中継にして SAML → OpenID Connect 変換すればいけるはず。

理屈は分かっている。
あとは、ひたすら手を動かすだけ。


構成全体イメージ

今回の構成はかなりシンプルにした。

[ LINE WORKS (SAML IdP) ]
            ↓
        [ Keycloak ]
            ↓
        [ OpenWebUI ]

そして、これらのコンテナ管理を Portainer の Web UI で一元管理

Docker を CLI だけで触るのも悪くないが、
構成が増えてくると、GUI 管理のありがたさが身に染みる。


Portainer での基本方針

docker-compose を Portainer の「Stack」機能で管理

流れとしては、

  1. Portainer にログイン
  2. 「Stacks」 → 「Add stack」
  3. docker-compose.yml を貼り付け
  4. デプロイ

これだけ。

サーバに SSH して compose 叩くより、
履歴管理・再デプロイ・設定の見直し が圧倒的に楽。


OpenWebUI の Stack 定義例

まず OpenWebUI 側。

※ 実際の IP・ドメインは伏せて、サンプルに書き換えている。

version: "3.9"

services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:0.6.36
    container_name: open-webui
    volumes:
      - data:/app/backend/data
    ports:
      - "8080:8080"
    extra_hosts:
      - host.docker.internal:host-gateway
    restart: unless-stopped
    environment:
      # --- OIDC / SSO 設定 ---
      OAUTH_CLIENT_ID: "openwebui"
      OAUTH_CLIENT_SECRET: "dummy-client-secret"
      OPENID_PROVIDER_URL: "https://auth.example.local/realms/openwebui/.well-known/openid-configuration"
      OAUTH_PROVIDER_NAME: "SSO Login"
      OAUTH_SCOPES: "openid email profile"
      OPENID_REDIRECT_URI: "https://openwebui.example.local/oauth/oidc/callback"

      # --- ユーザー管理 ---
      ENABLE_OAUTH_SIGNUP: "true"
      DEFAULT_USER_ROLE: "user"
      ENABLE_LOGIN_FORM: "false"

      # --- 表示 ---
      DEFAULT_LOCALE: "ja-JP"

      # --- セキュリティ ---
      WEBUI_SECRET_KEY: "dummy-secret-key"

volumes:
  data: {}

ポイントは、

  • ENABLE_OAUTH_SIGNUP=true
  • OPENID_PROVIDER_URL

ここで Keycloak と連携する。

OpenWebUI は メールアドレス必須 なので、
後述する Keycloak 側で username + .local 付与 という少し強引な加工をしている。


Keycloak の Stack 定義例

次に Keycloak。

version: "3.8"

services:
  db:
    image: postgres:16-alpine
    container_name: keycloak-db
    environment:
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: dummy-db-password
      POSTGRES_DB: keycloakdb
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: unless-stopped

  keycloak:
    image: quay.io/keycloak/keycloak:26.4.2
    container_name: keycloak
    command: >
      start
    environment:
      # --- 管理者アカウント ---
      KC_BOOTSTRAP_ADMIN_USERNAME: admin
      KC_BOOTSTRAP_ADMIN_PASSWORD: dummy-admin-password

      # --- DB接続 ---
      KC_DB: postgres
      KC_DB_URL_HOST: db
      KC_DB_URL_PORT: 5432
      KC_DB_URL_DATABASE: keycloakdb
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: dummy-db-password

      # --- リバースプロキシ前提 ---
      KC_HTTP_ENABLED: "true"
      KC_PROXY_HEADERS: xforwarded
      KC_HOSTNAME: auth.example.local

      # --- 機能拡張 ---
      KC_FEATURES: "hostname:v2,scripts"

volumes:
  db_data:

DB分離、リバースプロキシなどを考慮している 構成。


LINE WORKS (SAML2) 連携のクセ

今回一番ハマったのがここ。

LINE WORKS のユーザー名は、

user01@company

という ドメイン形式ではない 表記。

一方、OpenWebUI 側は メール形式必須

そのため Keycloak 側で、

${username}.local

という形で 強制的にメール形式へ変換 した。

Keycloak の Mapper 設定で、

  • 名前:nameid
  • タイプ:Username Template Importer
  • テンプレート:${NAMEID}.local

という形にして、無理やり整形。

正直、美しい構成ではない。
でも 現場では「動くこと」が最優先

この妥協は、かなり現実的な判断だったと思っている。


実際にやってみた感想

正直に言うと、かなり大変だった。

  • SAML メタデータのやり取り
  • 証明書
  • リダイレクト URL
  • Attribute Mapping

一つズレると、エラーしか返ってこない。

それでも、

  • 一度構成が完成すれば
  • ユーザー管理は LINE WORKS 側だけ
  • OpenWebUI 側の管理工数はほぼゼロ

ここまで運用が楽になると、
最初の苦労は十分に回収できる と感じた。


今の運用スタンス

今は、

  • OpenWebUI → ほぼ放置
  • ユーザー管理 → LINE WORKS 側のみ
  • Docker 管理 → Portainer の GUI

この形でかなり安定している。

「ひとり情シス」で一番大事なのは、
頑張らなくても回る仕組みを作ること

技術的に面白い構成より、
自分の手離れが良い構成 を優先した方が、
長期的には絶対に楽になる。


まとめ:この構成を支えているサーバ環境

この構成は、

  • OpenWebUI
  • Keycloak
  • PostgreSQL
  • Portainer

と、コンテナ数もそれなりに多くなる。

個人的には、
最初からCPU・メモリに余裕を持たせた Xserver VPS にしておいたのは正解 だったと思っている。

また、今回のような OpenWebUI + SSO 構成は、ネットワークのレスポンスを強く求められる用途ではないため、
Xserver VPS の通常プランでも十分と判断した。

Xserver VPSのビジネスプランと通常プランの違いについては、以下の記事で触れている。

なぜ業務サーバにXserver VPSを選んだのか|ひとり情シスのリアルな選定理由
業務システム用のサーバをどうするか、ここ1〜2年くらいずっと悩んでいました。きっかけは、オンプレで動かしていた 古いSQL Server Express(Windows)環境の老朽化。業務システムの基盤で、画像保存機能もあり、FTPも稼働し...

ちなみに、別の記事(SQL Server での業務サーバー構築)では、パフォーマンスと安定性を重視してビジネスプランを選んでいる。


この環境の構成まとめ

Dify → OpenWebUI → SSO 構成まで。業務用 AI フロント環境を VPS 上で作った全体ログまとめ
業務で AI を使う環境を作るにあたって、最初から「これが正解だ」と思える構成があったわけではありません。むしろ、まずは Dify を触ってみる実運用を考えて OpenWebUI に切り替える人数が増える前提で SSO + GUI 管理構成...