本日1件のお問い合わせがありました。

✉️FIELDにお問い合わせ

システム開発

OllamaでローカルLLMを動かす|Docker Composeで WordPress / Laravel から自前AI APIとして使う

「AIを使いたいが、APIの従量課金が読めない」「プロンプトや社内データを外部に送りたくない」——この2つは、AIを業務システムに組み込もうとすると必ず出てくる話です。

この2つを同時に解決する現実的な選択肢が Ollama(オラマ) です。この記事では、Ollamaとは何かから、Docker Composeへの追加方法、WordPress / Laravel から実際に呼ぶ最小コード、そしてサーバースペックの目安までをまとめて解説します。

Ollamaとは何か

Ollama は、LLM(大規模言語モデル)を自分のPCやサーバー上で動かすための仕組みです。OpenAI API を呼ばず、モデル本体をダウンロードして手元で推論します。

重要なのは、単なるCLIツールではなく ローカルAPIサーバーが標準で立ち上がる点です。起動すると localhost:11434 で HTTP API が待ち受けるため、自作アプリからは今まで通り「普通のWeb API」として呼べます。

使い方自体は非常に単純で、

ollama run gemma3

のように実行するだけでモデルが動きます。gpt-oss、Gemma、DeepSeek、Qwen など多数のモデルに対応しています。利用できるモデル名とタグは更新が早いので、実際に使うときは公式のモデルライブラリで最新のタグを確認してください。


構成として何が変わるのか

これまでのクラウドAI利用は、こういう流れでした。

Webアプリ
   ↓
OpenAI API
   ↓
GPT
   ↓
API料金(従量課金)

Ollama の場合はこうなります。

Webアプリ
   ↓
localhost:11434
   ↓
Ollama
   ↓
Qwen / Gemma / gpt-oss / DeepSeek 等
   ↓
自分のPC・サーバーのCPU/GPU

つまり、API従量課金なし・インターネット接続なしでもAIを使える構成が作れます。ローカル実行であればプロンプトや回答が Ollama 側へ送信されないことは、公式FAQにも明記されています。

なお、同じ用途でよく名前が挙がるものに LM Studio があります。こちらもローカルLLMを動かせて、OpenAI互換APIとして自作アプリから利用できます。完全オフラインでの利用やローカルRAGにも対応しているので、GUIで試したい場合の選択肢になります。

ただし「Docker Compose を立ち上げるだけでモデルまで取得し、既存サイトからローカルAIをAPIとして呼ぶ」というサーバー常駐型の構成なら、Ollama + Docker Compose の方が素直です。以降はこの構成を前提に進めます。


既存の docker-compose.yml に1サービス追加する

すでに動いている docker-compose.yml に Ollama を1サービス足すだけで、他のコンテナからは Docker の内部DNSによって http://ollama:11434 で呼べるようになります。

services:

  app:
    build: .
    container_name: app
    depends_on:
      - ollama
    networks:
      - app-network

  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    volumes:
      - ollama_data:/root/.ollama
    restart: unless-stopped
    networks:
      - app-network

volumes:
  ollama_data:

networks:
  app-network:

この状態で、app コンテナから http://ollama:11434 にアクセスできます。Ollama は標準で HTTP API を提供しており、/api/generate/api/chat を利用できます。

ports を書いていないのはなぜか

上の例には意図的に ports: を書いていません。ここがサーバー構成では一番重要なポイントです。

Ollama のローカルAPIには標準で認証がありません。11434:11434 のようにホストへ公開すると、ファイアウォール次第ではインターネットから誰でもモデルを叩ける状態になります。同一Dockerネットワーク内のコンテナからしか使わないのであれば、ports: は書かないのが正解です。

インターネット
      ×
      │
┌─────┴──────────┐
│ Docker Server  │
│                │
│ Web/PHP        │
│    │           │
│    ↓           │
│ ollama:11434   │
│    │           │
│    ↓           │
│ Local LLM      │
└────────────────┘

こうしておけば、外部から Ollama を直接叩かれることはありません。開発中に手元から確認したい場合も、ホストへ公開するのではなく docker compose exec 経由か、127.0.0.1:11434:11434 のようにループバック限定でバインドするのが安全です。


モデルの取得と永続化

ここは見落としやすいのですが、Ollamaコンテナを追加しただけではモデル本体はまだありません。初回に取得が必要です。

docker compose exec ollama ollama pull gemma3

取得したモデルは、先ほど定義した Docker Volume に保存されます。

ollama_data
 ├─ モデル本体
 ├─ manifest
 └─ その他Ollamaデータ

Volume に載っているので、docker compose downdocker compose up -d しても毎回ダウンロードし直す必要はありません。逆に言うと、docker compose down -v で Volume ごと消すとモデルも消えるので注意してください。

up 一発でモデルまで準備する

「環境を作った人以外が pull を忘れる」のを防ぐには、初期化用サービスを分けて置いておくのが確実です。

services:

  app:
    build: .
    depends_on:
      ollama:
        condition: service_started

  ollama:
    image: ollama/ollama:latest
    volumes:
      - ollama_data:/root/.ollama
    restart: unless-stopped
    networks:
      - app-network

  ollama-init:
    image: ollama/ollama:latest
    depends_on:
      - ollama
    environment:
      - OLLAMA_HOST=http://ollama:11434
    entrypoint: >
      sh -c "
      until ollama list >/dev/null 2>&1; do sleep 2; done &&
      ollama pull gemma3
      "
    restart: "no"
    networks:
      - app-network

volumes:
  ollama_data:

networks:
  app-network:

ポイントは ollama-initOLLAMA_HOST を渡していることです。これにより init コンテナ自身がモデルを持つのではなく、ollamaコンテナに対して pull を指示する形になり、モデルは共有Volumeに落ちます。

また、固定の sleep 5 ではなく until ollama list で待つようにしています。起動が遅れたときに pull が空振りしないためです。

これで docker compose up -d だけで、

① Ollama起動
↓
② モデルDL
↓
③ API利用可能
↓
④ appから http://ollama:11434 を呼ぶ

という流れが自動で完了します。


WordPress から呼ぶ最小コード

ここからが実装です。OpenAI SDK すら使わず、普通のHTTP POST 1回で動きます。

WordPress には wp_remote_post() が標準搭載されているので、追加ライブラリは不要です。

<?php
function ask_local_ai($prompt) {

    $response = wp_remote_post(
        'http://ollama:11434/api/generate',
        [
            'timeout' => 60,
            'headers' => [
                'Content-Type' => 'application/json',
            ],
            'body' => json_encode([
                'model'  => 'gemma3:4b',
                'prompt' => $prompt,
                'stream' => false,
            ]),
        ]
    );

    if (is_wp_error($response)) {
        return 'AI接続エラー';
    }

    $body = json_decode(
        wp_remote_retrieve_body($response),
        true
    );

    return $body['response'] ?? '';
}

これだけです。呼び出しは、

echo ask_local_ai('函館について100文字で説明してください');

処理の流れはこうなります。

WordPress
    ↓
ask_local_ai()
    ↓
POST http://ollama:11434/api/generate
    ↓
Ollama
    ↓
Gemma
    ↓
生成結果
    ↓
WordPress

ショートコード化すれば、記事本文から直接呼べます。

add_shortcode('local_ai', function() {

    return ask_local_ai(
        '函館について100文字で説明してください'
    );

});

あとは記事に [local_ai] と書くだけで動きます。

注意:この書き方はページを表示するたびにAI推論が走ります。CPU推論だと1リクエストで数秒〜数十秒かかるため、本番では必ず set_transient() などでキャッシュを挟んでください。


Laravel から呼ぶ最小コード

Laravel は標準の HTTP Client がそのまま使えるので、さらに単純です。

use IlluminateSupportFacadesHttp;

$response = Http::timeout(60)->post(
    'http://ollama:11434/api/generate',
    [
        'model'  => 'gemma3:4b',
        'prompt' => '函館について100文字で説明してください',
        'stream' => false,
    ]
);

echo $response->json('response');

動作確認だけなら、ルートに直接書いてしまうのが早いです。

use IlluminateSupportFacadesHttp;
use IlluminateSupportFacadesRoute;

Route::get('/ai-test', function () {

    $response = Http::timeout(60)->post(
        'http://ollama:11434/api/generate',
        [
            'model'  => 'gemma3:4b',
            'prompt' => '函館について100文字で説明してください',
            'stream' => false,
        ]
    );

    return $response->json('response');

});

ブラウザで /ai-test を開けば、こう流れます。

ブラウザ
   ↓
Laravel
   ↓
http://ollama:11434
   ↓
Ollama
   ↓
gemma3:4b
   ↓
回答

本番では Service に切り出す

URLやモデル名をベタ書きすると環境ごとの切り替えができないので、Service にまとめて設定から読むようにします。

app/Services/OllamaService.php

<?php

namespace AppServices;

use IlluminateSupportFacadesHttp;

class OllamaService
{
    public function ask(string $prompt): string
    {
        $response = Http::timeout(60)->post(
            config('services.ollama.url') . '/api/generate',
            [
                'model'  => config('services.ollama.model'),
                'prompt' => $prompt,
                'stream' => false,
            ]
        );

        return $response->json('response') ?? '';
    }
}

Controller 側はこれだけになります。

use AppServicesOllamaService;

public function test(OllamaService $ai)
{
    return $ai->ask(
        '函館について100文字で説明してください'
    );
}

.env には次のように書いておきます。

OLLAMA_URL=http://ollama:11434
OLLAMA_MODEL=gemma3:4b

WordPress と Laravel の違い

項目WordPressLaravel
HTTP通信wp_remote_post()Http::post()
Ollama URLhttp://ollama:11434429430http://ollama:11434431432433434APIキー435436不要437438不要439440441442OpenAI契約443444不要445446不要447448449450OpenAI SDK不要不要
インターネット推論時は不要推論時は不要
推奨実装プラグイン化Service化
非同期処理WP Cron 等Queue が使いやすい

表にすると差があるように見えますが、本質は「HTTPリクエストを1回投げるだけ」で共通です。Ollama から見れば、呼び出し元が WordPress か Laravel かは関係ありません。

                  ┌→ WordPress
                  │
ユーザー → Web ───┤
                  │
                  └→ Laravel
                       │
                       ↓
               http://ollama:11434
                       │
                       ↓
                    Ollama
                       │
                       ↓
                  ローカルLLM

既存の OpenAI API 実装を流用する

すでに OpenAI API 前提でコードを書いてしまっている場合も、作り直す必要はありません。Ollama には OpenAI 互換APIがあるため、多くの場合はエンドポイントの差し替えだけで済みます。

https://api.openai.com/v1
        ↓ 差し替え
http://ollama:11434/v1

例えば既存コードがこうなっているなら、

const client = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY
});

概念的には、こう変更できます。

const client = new OpenAI({
    baseURL: "http://ollama:11434/v1",
    apiKey: "ollama"
});

ローカルの Ollama API 自体に APIキー認証はありません。ただし OpenAI SDK は apiKey が空だと例外を投げるので、ダミー値を入れておく必要があります。

「互換」であってGPTと同一ではありません。関数呼び出し(tool calling)やレスポンス形式の細部はモデル依存で挙動が変わります。差し替えたら必ず実際のレスポンスで検証してください。


どのくらいのサーバースペックが必要か

ここが導入判断の分かれ目です。目安は「モデルの重さ(ディスク)」と「実行時RAM」を分けて考えると整理しやすくなります。

モデル規模ディスクRAM
1B〜3B級2〜5GB4〜8GB
7B〜8B級5〜10GB8〜16GB
14B級10〜20GB16〜32GB
30B級20〜40GB以上32〜64GB
70B級40〜80GB以上64GB以上

Ollama では量子化されたモデルを使うことが多いため、「7Bモデルだから7GB固定」ということはありません。量子化方式によってかなり軽くできます。

用途別に整理すると、現実的にはこのあたりです。

  • 軽い分類・要約・タグ付け:RAM 8GB / 空きディスク 10〜20GB / GPUなし可 → 1B〜3B級
  • 普通の文章生成・問い合わせ回答:RAM 16GB / 空きディスク 20〜30GB / GPUなし可 → 7B〜8B級
  • 少し賢さを求める:RAM 32GB / 空きディスク 30〜50GB → 14B級
  • かなり高性能:RAM 64GB以上 / 空きディスク 60GB以上 → 30B級以上

重要なのは、ディスク容量よりRAMだという点です。空きディスクが100GBあっても、RAMが4GBしかなければ大きいモデルはまともに動きません。

逆に、CPU 4〜8 vCPU / RAM 16GB / GPUなし / ディスク空き30GB くらいあれば、Ollama を Web サービスから API として使う構成は十分に成立します。

ただし GPU なしだと、7B級でも回答速度は CPU 性能次第でかなり差が出ます。「GPT-4のような対話AI」ではなく、社内ツール・要約・分類・RAG・数秒〜数十秒待てる文章生成といった用途向けだと考えてください。大量同時アクセスをさばく用途には向きません。

自分のサーバーを確認するコマンド

Linuxサーバーなら、SSHで入って以下を実行すれば一通り分かります。

echo "===== RAM ====="
free -h

echo ""
echo "===== CPU ====="
lscpu | grep -E "Model name|CPU(s)|Core|Thread"

echo ""
echo "===== GPU ====="
if command -v nvidia-smi >/dev/null 2>&1; then
    nvidia-smi
else
    echo "NVIDIA GPUなし、またはnvidia-smi未導入"
fi

echo ""
echo "===== DISK ====="
df -h /

例えば結果がこうなら、

===== RAM =====
               total
Mem:            15Gi

===== CPU =====
CPU(s):          4
Model name:      AMD EPYC 7R32

===== GPU =====
NVIDIA GPUなし

===== DISK =====
Filesystem      Size  Used Avail
/dev/xvda1      100G   40G   60G

RAM 15GB / CPU 4 vCPU / GPUなし / 空きディスク 60GB ということになります。上の表に当てはめると 7B〜8B級が現実的なライン、という判断ができます。


まとめ

要点を整理します。

  • Ollama は localhost:11434 に HTTP API を立てるので、既存アプリからは「普通のAPI」として扱える
  • 既存の docker-compose.yml に1サービス追加するだけで、http://ollama:11434 で呼べる
  • モデルは Volume に永続化されるので、初回の pull だけで済む
  • init サービスを分ければ docker compose up -d 一発でモデルまで準備できる
  • 実装の本質は wp_remote_post() / Http::post() によるHTTPリクエスト1回
  • OpenAI互換APIがあるので、既存のOpenAI実装はエンドポイント差し替えで流用しやすい
  • 11434 を外部公開しない。認証がないため、同一Dockerネットワーク内に閉じる
  • 効くのはディスクよりRAM。まずは RAM 16GB + 7B前後 から始めるとバランスが良い

「既存システムのOpenAI APIを呼んでいる部分を、ローカルのOllamaに向ける」——導入のイメージとしては、まずこの理解で問題ありません。

FIELDでは、既存サイト・既存システムへのローカルAI組み込みや、サーバースペックを踏まえた構成の設計もお手伝いしています。「うちのサーバーで何ができるか」からご相談いただけます。

Yamamoto Yuya

プロフェッショナルとしての高いスキルと知識を持ち、誠実さと責任感を大切にする。常に向上心を持ち、新たな挑戦にも積極的に取り組む努力家。