Ollama + Open WebUI:ローカルLLM構築【2026年完全ガイド】

ローカルで LLM を動かすなら Ollama + Open WebUI が標準。2025年の古い記事では断片的だったが、2026年版は実装から運用まで全て網羅。


なぜローカル LLM なのか

【クラウド API(ChatGPT など)】

  • 毎回データが外に出る(セキュリティ問題)
  • API 代がかさむ(月数千~数万円)
  • 速度が遅い場合がある(ネットワーク遅延)

【ローカル LLM(Ollama)】

  • データが自分のサーバーだけ(セキュアー)
  • 無料(初期構築後、ランニング費用ほぼゼロ)
  • 速度が速い(ネットワーク遅延ゼロ)

必要な環境

ハードウェア

【最小構成】

  • CPU:4コア以上(Ryzen 5 相当)
  • メモリ:8GB(推奨 16GB)
  • ストレージ:30GB(モデルサイズ次第)

【GPU あると大幅に高速化】

  • Nvidia GPU:RTX 3060 以上推奨
  • AMD GPU:RX 6700 XT 以上推奨
  • Mac M1/M2:統合 GPU で十分

【GPU なし場合】

  • CPU のみでも動作(ただし遅い)
  • 推論速度:1トークン/秒程度
  • GPU あり場合:10~20倍高速

ソフトウェア

【必須】

  • Docker
  • Docker Compose

【推奨(GPU使用時)】

  • Nvidia GPU ドライバー
  • CUDA Toolkit 12.0+

インストール手順

ステップ 1:Docker & Docker Compose インストール

# Ubuntu/Debian
sudo apt-get update
sudo apt-get install docker.io docker-compose

# 権限設定(sudo なしで実行可能に)
sudo usermod -aG docker $USER
newgrp docker

ステップ 2:docker-compose.yml 作成

version: '3.8'

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
    gpus:
      - driver: nvidia
        all: true  # GPU 全て使用
    restart: always

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    ports:
      - "8080:8080"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
    depends_on:
      - ollama
    restart: always
    volumes:
      - webui_data:/app/backend/data

volumes:
  ollama_data:
  webui_data:

ステップ 3:起動

docker-compose up -d

# ログ確認
docker-compose logs -f

ステップ 4:モデルのダウンロード

# Ollama コンテナに入る
docker exec -it ollama bash

# モデルをダウンロード
ollama pull llama2:7b
# または
ollama pull mistral:7b
# または
ollama pull neural-chat:7b

ステップ 5:Open WebUI にアクセス

ブラウザで http://localhost:8080 を開く
→ ユーザー登録(初回のみ)
→ モデル選択して利用開始

GPU 設定(高速化のため)

Nvidia GPU の場合

# CUDA Toolkit インストール
sudo apt-get install cuda-toolkit-12-0

# docker-compose.yml で GPU を有効化(上記の例参照)
services:
  ollama:
    gpus:
      - driver: nvidia
        all: true

GPU が認識されているか確認

# コンテナ内で確認
docker exec -it ollama bash
ollama run llama2:7b

# プロンプトで GPU 使用状況を確認
# "GPU acceleration enabled" と表示されたら OK

推奨モデル一覧(2026年)

モデル サイズ 速度 品質 用途
Llama 2 7B 4GB 速い 標準 汎用・最初の1択
Mistral 7B 4GB 高速 高い 日本語・コード
Neural Chat 7B 4GB 速い 高い 会話型・日本語
Llama 2 13B 8GB 中程度 高い より精度重視
Code Llama 7B 4GB 速い コード特化 プログラミング
Mistral Large 34GB 遅い 非常に高い 重い処理用

初心者推奨:Mistral 7B(バランス型)


よくある落とし穴

1. GPU が認識されない

# 確認コマンド
docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi

# 出力がなければ、nvidia-docker が必要
sudo apt-get install nvidia-docker2
sudo systemctl restart docker

2. メモリ不足エラー

"OOM Killed" または メモリ不足エラーが出た場合

対策 1:小さいモデルを使う

ollama pull llama2:7b  # 7B は 4GB
# ではなく
ollama pull phi:2.7b   # 2.7B は 2GB

対策 2:スワップメモリを増やす

sudo fallocate -l 16G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

3. Open WebUI が Ollama に接続できない

# docker-compose.yml で
environment:
  - OLLAMA_BASE_URL=http://ollama:11434

# これが正しくないと接続失敗
# ホスト名は「ollama」(service 名)
# ポートは 11434(Ollama のデフォルト)

4. 応答が遅い(GPU 未使用の場合)

# GPU が使われているか確認
docker exec -it ollama ollama ps

# "GPU acceleration disabled" が出たら GPU 未認識
→ 上記「GPU が認識されない」の対策を実施

実装時のベストプラクティス

1. モデルの永続化

volumes:
  ollama_data:/root/.ollama  # ← ここにモデルが保存される

# コンテナ削除後も モデルが残る
docker-compose down  # コンテナ削除
docker-compose up -d  # 再起動(モデルダウンロード不要)

2. Web UI のセキュリティ設定

open-webui:
  environment:
    - OLLAMA_BASE_URL=http://ollama:11434
    # ローカルホストのみアクセス許可
    - WEBUI_AUTH_REQUIRED=true  # ユーザー認証必須

3. 複数モデルの管理

# ダウンロード済みモデルを確認
docker exec -it ollama ollama list

# 不要なモデルを削除
docker exec -it ollama ollama rm llama2:7b

4. バックアップ

# モデルデータをバックアップ
docker cp ollama:/root/.ollama ./ollama_backup

# WebUI のデータ(会話履歴)をバックアップ
docker cp open-webui:/app/backend/data ./webui_backup

トラブルシューティング

ポート競合

"Address already in use" エラーが出た場合

docker ps  # 既存コンテナを確認
docker stop <コンテナ ID>
docker-compose up -d  # 再起動

メモリリーク

# コンテナのメモリ使用量を監視
docker stats

# メモリが増え続ける場合、モデルを再ロード
docker restart ollama

ネットワーク接続の問題

# コンテナ間の通信確認
docker exec -it open-webui ping ollama

# 失敗する場合、ネットワークドライバを確認
docker network ls
docker network inspect <network_name>

2025年版との変更点

項目 2025年 2026年
モデルサイズ Llama 2 のみ 複数選択肢
GPU 設定 簡潔 詳細なトラブルシューティング
セキュリティ 未記載 ユーザー認証設定
バックアップ 未記載 手順明記
トラブルシューティング なし 充実

次のステップ

【1日目】

  • Ollama + Open WebUI をセットアップ
  • モデル 1つをダウンロード・テスト

【1週間後】

  • GPU 最適化を試す
  • 複数モデルを試して相性を確認

【1ヶ月後】

  • 実運用に向けて、セキュリティ設定を強化
  • API 化(他アプリから Ollama を呼び出し)を検討

参考資料

Android Jetpack Compose LazyGrid で斜めスクロール実装【2026年版】

Android 斜めスクロールView実装 ~2026年Jetpack Compose版

📌 関連記事: この記事は 【android】ScrollViewで縦横斜めにスクロール(2015年)の2026年版アップデートです。当時のコンセプトが、今どう実装されているかをご紹介します。

かつて「Androidで斜めスクロールって実装できないのか?」という問い合わせがあった。2015年時点では標準APIになく、自作が必須だった。今は Jetpack Compose の登場で、状況が大きく変わった。

2026年なら、Compose + LazyGrid で 1行で解決できる。


昔の問題(2015年)vs 現在(2026年)

2015年:框組みで解決しようとしていた時代

ScrollView(縦)+ HorizontalScrollView(横)
  ↓
マージして「斜めスクロール」を実現
  ↓
カスタムViewGroupを自作

当時は効果測定も「コンセプト段階」で、実装されていなかった。

2026年:Jetpack Compose なら標準

LazyVerticalGrid / LazyHorizontalGrid
  ↓
フリングスクロール、慣性スクロール完備
  ↓
nested scroll も自動対応

標準APIs の充実で、カスタム実装が不要になった。


2026年の実装:Jetpack Compose

ステップ 1:依存関係の追加

dependencies {
    implementation "androidx.compose.foundation:foundation:1.7.0"
    implementation "androidx.compose.material3:material3:1.3.0"
    implementation "androidx.activity:activity-compose:1.9.0"
}

ステップ 2:LazyGrid で斜めスクロール実現

@Composable
fun DiagonalScrollView() {
    LazyVerticalGrid(
        columns = GridCells.Fixed(3),
        modifier = Modifier
            .fillMaxSize()
            .padding(8.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(100) { index ->
            GridItem(index)
        }
    }
}

@Composable
fun GridItem(index: Int) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .height(120.dp),
        colors = CardDefaults.cardColors(
            containerColor = Color(0xFF6200EE)
        )
    ) {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "Item $index",
                color = Color.White,
                fontSize = 16.sp,
                fontWeight = FontWeight.Bold
            )
        }
    }
}

ステップ 3:複数方向スクロール(横 + 縦)

単純な縦スクロール + 横スクロール両対応が必要なら:

@Composable
fun BiDirectionalScroll() {
    LazyVerticalGrid(
        columns = GridCells.Fixed(4),
        modifier = Modifier
            .fillMaxSize()
            .horizontalScroll(rememberScrollState())
    ) {
        items(200) { index ->
            GridItem(index)
        }
    }
}
注意:
  • horizontalScroll()LazyVerticalGrid でも使用可能
  • フリングスクロール、慣性スクロールは自動対応
  • パフォーマンスは Compose の仮想化により最適化済み

実装時の落とし穴

1. NestedScrollConnection の設定を忘れずに

親スクロールと子スクロールが両方ある場合:

val nestedScrollDispatcher = remember { NestedScrollDispatcher() }

LazyVerticalGrid(
    columns = GridCells.Fixed(3),
    modifier = Modifier
        .nestedScroll(nestedScrollDispatcher.asNestedScrollConnection())
) {
    // ... items
}

2. パフォーマンス:アイテム数が大きい場合

❌ 悪い例:すべてのアイテムを描画
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
    repeat(10000) { index ->
        GridItem(index)  // 全項目メモリに残る
    }
}
✅ 良い例:仮想化による遅延描画
LazyVerticalGrid(columns = GridCells.Fixed(3)) {
    items(10000) { index ->
        GridItem(index)  // 見える範囲だけ描画
    }
}

3. スクロール位置の保存

ユーザーが別の画面から戻ってきた時、スクロール位置を復元:

val listState = rememberLazyGridState()

LazyVerticalGrid(
    columns = GridCells.Fixed(3),
    state = listState
) {
    items(100) { index ->
        GridItem(index)
    }
}

// スクロール位置を保存
LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .collect { index ->
            // ローカルDBに保存
            saveScrollPosition(index)
        }
}

2015年実装 vs 2026年実装:比較

項目 2015年 2026年
フレームワーク View(XML + Java) Jetpack Compose(Kotlin)
コード行数 100~200行 30~50行
パフォーマンス メモリ負荷大(全アイテム保持) 軽量(仮想化)
実装難度 中~高(カスタムViewGroup必須) 低(標準APIで解決)
フリングスクロール 手実装 自動対応
nested scroll対応 複雑 シンプル
テスト容易性 高(Composable テスト用ツール充実)

代替案:特殊な要件がある場合

要件 1:斜め45度のカスタムスクロール

@Composable
fun DiagonalCustomScroll(
    angle: Float = 45f  // 45度斜めスクロール
) {
    LazyVerticalGrid(
        columns = GridCells.Fixed(3),
        modifier = Modifier
            .fillMaxSize()
            .rotate(angle)  // 斜めに回転
    ) {
        items(100) { index ->
            GridItem(index)
        }
    }
}

要件 2:スクロール速度をカスタマイズ

@Composable
fun CustomSpeedScroll() {
    val scrollState = rememberScrollState()

    Column(
        modifier = Modifier
            .verticalScroll(
                scrollState,
                flingBehavior = ScrollableDefaults.flingBehavior()
            )
    ) {
        repeat(100) { index ->
            Text("Item $index", modifier = Modifier.padding(16.dp))
        }
    }
}

まとめ

2015年:「カスタムViewGroup自作が必須」

2026年:「Compose の LazyGrid で 30行で完成」

Android 開発は急速に進化している。かつての「困難な実装」が「標準機能」に変わるのは珍しくない。

古い記事の実装方法に固執するのではなく、2026年のプラットフォーム能力を活用する方が圧倒的に効率的。


参考資料

最近、Excel で選択したセルの内容を Ollama で要約・添削できる Add-in を作ってみた

最近、Excel で選択したセルの内容を Ollama で要約・添削できる Add-in を作ってみた。

正直なところ、想定外の落とし穴がいっぱいあった。特に「ローカルネットワーク別 PC の Ollama に、Excel Add-in から安全にアクセスする」という部分が、予想以上に複雑だった。その試行錯誤プロセスを記録しておく。


背景:なぜこんなもの作ったのか

仕事で長めの文章を扱うことが多い。メール、レポート、提案書とか。

最初は ChatGPT API で要約・添削をやってみたけど、3 つの理由でやめた:

  1. データを外に出したくない - 機密文書を API に送るのは避けたい
  2. API コストが地味に積もる - 毎日使うと月額が結構かかる
  3. レスポンス待ちが遅い - 社内 LAN 上なら圧倒的に速い
そこで目を付けたのが Ollama。ローカルネットワーク別 PC で Ollama を建てて、そこに直接アクセスできれば、データも保護できるし、レスポンスも速い。

「Excel から直接使えたら最高だな」と思いついて、Add-in を作り始めたのが始まり。


技術構成

実装してみた構成は以下:

【Excel Add-in】
  ↓ HTTPS でロード
https://localhost:8888(React UI の配信)
  ↓ HTTP リクエスト
【Go バックエンド】(localhost:8888)
  ↓ ローカルネットワーク経由
【Ollama API】(http://172.19.10.10:11434)

バックエンド: Go(標準 net/http パッケージ)

  • シンプルに HTTP サーバーを立てるだけなので、フレームワークは不要だと判断
  • 自己署名証明書での HTTPS 対応

フロント: React

  • Office JavaScript API で Excel セルの読み書き
  • Go API への HTTP 通信

Ollama

  • ローカルネットワーク別 PC 上で実行
  • セルの内容を送信 → 要約・添削結果を取得

予想外の落とし穴:Excel Add-in の HTTPS 必須ポリシー

実装し始めてすぐにぶつかったのが、Excel Add-in は HTTP ではなく HTTPS が必須という制約。

「localhost で動かすんだから HTTP でいいか」と思ってた。甘かった。

さらに複雑だったのが、Ollama が ローカルネットワーク別 PC にあること。

構図:
  PC A(Excel を使ってる)
    ↓
  自分の PC(Go + React のサーバー)
    ↓ ローカルネットワーク
  PC B(172.19.10.10 に Ollama が動いてる)

やりたいこと:

  • Excel Add-in の UI(HTML/React)を自分の PC から HTTPS で配信
  • Add-in から Ollama(別 PC)に HTTP でアクセス

問題:

  • Excel Add-in は HTTPS 配信が必須だけど、自己署名証明書では「信頼されていない」と判定される
  • 別 PC の Ollama にはネットワーク経由でアクセスする必要がある

解決策 1:オレオレ証明書を localhost で動かす

まず自己署名証明書を生成した。

openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes \
  -subj "/CN=localhost"

この証明書と秘密鍵で、Go の net/http サーバーを立ち上げる。

package main

import (
    "net/http"
)

func main() {
    // ハンドラーを登録
    http.HandleFunc("/api/summarize", handleSummarize)
    http.HandleFunc("/api/proofread", handleProofread)

    // HTTPS で起動(自己署名証明書使用)
    http.ListenAndServeTLS(":8443", "server.crt", "server.key", nil)
}

これで https://localhost:8443 で HTTPS サーバーが起動。

ただし、ここまでではまだ不十分。Excel Add-in のコンテキストでは、この自己署名証明書が「信頼されていない」と判定される。


解決策:自己署名証明書 + ネットワークルーティング

ここが工夫の見せどころ。

2 つのポイントで解決した:

  1. localhost で自己署名証明書を使った HTTPS サーバー起動 - Excel Add-in の HTTPS 必須要件を満たす
  2. Go バックエンド - HTML/React を配信しつつ、Ollama への API リクエストを中継
【構図】
Excel Add-in
  ↓ HTTPS でロード
https://localhost:8888
  ↓ HTML/React の配信 + API エンドポイント
【Go サーバー】
  ├─ /(HTML/React UI を配信)
  └─ /api/summarize, /api/proofread(Ollama に転送)
    ↓ HTTP でローカルネットワーク経由
http://172.19.10.10:11434
  ↓
【Ollama API】

実装

まず自己署名証明書を生成。

openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes \
  -subj "/CN=localhost"

Go のサーバーコード:

package main

import (
    "net/http"
    "net/http/httputil"
    "net/url"
)

func main() {
    // Ollama API へのリバースプロキシを設定
    ollamaURL, _ := url.Parse("http://172.19.10.10:11434")
    reverseProxy := httputil.NewSingleHostReverseProxy(ollamaURL)

    // HTML/React UI を配信
    fs := http.FileServer(http.Dir("./public"))
    http.Handle("/", fs)

    // /api/generate は Ollama に中継
    http.HandleFunc("/api/generate", func(w http.ResponseWriter, r *http.Request) {
        reverseProxy.ServeHTTP(w, r)
    })

    // HTTPS で起動(自己署名証明書)
    http.ListenAndServeTLS(":8888", "server.crt", "server.key", nil)
}

React 側から Ollama API を呼び出すときは、相対パス /api/generate を使う。

// React コンポーネント
const handleSummarize = async (cellContent) => {
    const response = await fetch('/api/generate', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            model: 'llama2:7b',
            prompt: `要約してください:\n\n${cellContent}`,
            stream: false
        })
    });

    const result = await response.json();
    return result.response;
};

重要なポイント:

  • localhost:8888 で自己署名証明書を使った HTTPS サーバーを起動
  • Excel Add-in のセキュリティ要件(HTTPS 配信)を満たす
  • React は相対パス /api/generate で Ollama に通信(実際には Go バックエンドが仲介)
  • Go バックエンド内で、localhost から 172.19.10.10 へのネットワークルーティングを実現
  • シンプル:プロキシツール不要で、Go だけで実装完結

セルを選択→要約・添削:実装の流れ

実装が決まったら、次は実用機能。

Excel で文章が入ってるセルを選択して、「要約」「添削」ボタンを押すと、Ollama で処理してセルに結果を返す。

流れ:

  1. セル選択時のイベント取得
await Excel.run(async (context) => {
    const cell = context.application.getSelectedData();
    // セルの内容を取得
});
  1. Go API に送信
const summarized = await fetch('https://localhost:8888/api/summarize', {
    method: 'POST',
    body: JSON.stringify({ text: cellContent })
});
  1. Ollama から要約結果を取得
  • モデルはデフォルトで llama2:7b を使用
  • プロンプトエンジニアリングで「要約」「添削」を区別
  1. 結果をセルに書き込み
await Excel.run(async (context) => {
    context.worksheets.getActiveWorksheet()
        .getRange("A1")
        .values = [[result]];
});

実装時の工夫:

  • ローディング表示 - Ollama はレスポンスに数秒かかるので、「処理中...」を表示
  • エラーハンドリング - ネットワーク接続や Ollama が落ちてる場合の対応
  • タイムアウト設定 - 長すぎる文章は処理に時間がかかるので、上限を設ける
  • プロンプト最適化 - 同じモデルでも「要約」「添削」でプロンプトを変える
// 要約用プロンプト
func getSummarizePrompt(text string) string {
    return fmt.Sprintf(
        "以下のテキストを簡潔に要約してください。箇条書きで3点まで。\n\n%s",
        text,
    )
}

// 添削用プロンプト
func getProofreadPrompt(text string) string {
    return fmt.Sprintf(
        "以下のテキストの文法、表現、敬語を確認して、改善案を提示してください。\n\n%s",
        text,
    )
}

実際に使ってみて

完成して 1 ヶ月ほど使ってみた感想。

便利だったこと:

  1. データが外に出ない - 機密文書を扱うときは本当に安心。API に送ってる時間を気にしなくていい
  2. レスポンスが速い - ローカルネットワーク経由なので、ChatGPT API より圧倒的に高速。待ち時間がストレスにならない
  3. コストがゼロ - Ollama はローカル実行なので追加コストなし。PC のリソース(GPU)を使ってる分だけ

予想外だったこと:

  • 要約の質が思ったより良い - llama2:7b で十分実用的。わざわざ大型モデルに乗り換える必要がない
  • 添削より要約の方が使用頻度が高い - 最初は「添削機能メインで作ろう」と思ってたけど、実用では「要約」がメイン用途に
  • 複数人での利用が想定より難しい - 自分の PC でプロキシを立ててるので、他の PC からはアクセスできない。チーム展開するなら、プロキシを別サーバーに移す必要があった

今後の改善案:

  • Ollama の複数モデル対応 - llama2 以外の軽量モデル(mistral など)も選べるようにしたい
  • Web UI での設定画面 - 今は Go コード内でハードコードしてる設定を、UI で変更できるように
  • ローカルプロキシをサーバー化 - チーム利用を想定して、プロキシを別サーバーに移す

技術的な学び

このプロジェクトで学んだことが 3 つ。

1. Excel Add-in のセキュリティ要件は思ったより厳しい

Excel が HTTPS・自己署名証明書・CORS まで厳密にチェックする。SPA(Single Page Application)の世界と違って、企業向けアプリケーションの厳格さを感じた。

2. ローカル接続は「仲介役」として超有効

別ネットワークにある Ollama にアクセスする時、直接アクセスではなくローカルサーバー経由にすることで、セキュリティと利便性のバランスが取れた。

3. ローカル LLM の強さ

API の待ち時間がゼロに等しく、コストもかからない。確度は ChatGPT より落ちるかもしれないが、実務用途(要約・添削)には十分。「LLM の大型化よりローカル化の時代が来たのかも」と感じた。


まとめ

「Go + React + Ollama」という組み合わせで Excel Add-in を作った。

最大の工夫は「自己署名証明書 + ネットワークルーティング」という組み合わせで、ローカルネットワーク別 PC の Ollama に安全にアクセスする仕組み。

完成してみると、想定外の利便性があった。特に「データが絶対外に出ない」という安心感と「レスポンスの速さ」は、クラウド API では代替できない価値。

チーム展開や本番運用となると、プロキシのサーバー化やアーキテクチャの見直しが必要だけど、個人ツールレベルなら「自己署名証明書 + ネットワークルーティング」で十分実用的。

ローカル LLM の活用方法は、これからも増えていくんだろうなと思った。