Docker Model RunnerでLLMアプリ開発のAPIコストを削減しよう

最近、Docker Desktop 4.40のアップデートで、Mac Apple Silicon(M1~M4)向けにDocker Model Runnerのβ版が使えるようになりました。

https://docs.docker.com/desktop/features/model-runner

私のように OpenAI APIやBedrockをアプリケーションに組み込んでいる方ならわかると思いますが、ローカル環境での開発時にこれらのAPIを使い続けると、チーム開発では意外とコストがかさみます。

また、最近話題のLLM(Gemma 3、LLaMA 3.3)など、気になるオープンソースモデルを気軽にローカルで試したい場面も多いと思います。

Docker Model Runnerは、そんな悩みを解消してくれる新機能となります。

ちなみにこちらが元記事です。かなり反響があって驚いています。

https://dev.to/suzuki0430/the-easiest-way-to-set-up-mcp-with-claude-desktop-and-docker-desktop-5o

## Docker Model Runnerとは？
Docker Model Runnerは、Docker Desktopに統合されたローカルで動作するOpenAI API互換の推論サーバーです。

例えば、アプリケーションでOpenAI SDKを使っていれば、エンドポイントのURLを変更するだけでDocker Model Runnerのモデルを利用できます。

```ts
new OpenAI({
  apiKey: 'dummy',
  baseURL: 'http://model-runner.docker.internal/engines/v1'
})
```

OllamaもローカルLLMを扱えるツールですが、APIはOpenAI互換ではないため、SDKを使いたい場合はDocker Model Runnerの方が断然便利です。

また、Docker Hubから気になるモデルをdockerコマンドで簡単にダウンロードできたりします(`ex. docker model pull ai/gemma3`)。

## 本番環境はクラウド、開発環境はローカル
Docker Model Runnerの最も効果的な使い方は、開発環境と本番環境を使い分けることです。

私のケースでは、本番環境はAmazon Bedrockを利用していますが、ローカルでプロンプト調整やテストを行う際にBedrockを叩くのは非効率かつ高コストなので、開発環境ではDocker Model Runnerへ切り替える構成にしました。本番環境でOpenAI APIやVertex AIなど使用している場合も同様の構成となります。

## 導入手順

### 1. Docker Desktopをアップデート
Docker Model RunnerはDocker Desktop 4.40以降の機能です。バージョンが古い場合はアップデートしましょう。

![スクリーンショット 2025-04-10 20.22.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/47840b69-4128-4a5b-9ac4-30fbe2196a65.png)

### 2. Docker Model Runnerを有効化
Docker Desktopで、`Settings > Features in development > Beta features > Enable Docker Model Runner`をONにします。

![スクリーンショット 2025-04-10 20.23.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/fdfa1996-0d16-4a20-8084-68f8a6f118b0.png)

CLIでも有効化できます。

```bash
$ docker desktop enable model-runner
```

### 3. モデルをローカルにpull
Docker Hubで使いたいモデルを選択します。

https://hub.docker.com/u/ai

以下のコマンドでモデルをローカル環境にプルします。

```bash
$ docker model pull ai/gemma3:1B-Q4_K_M
```

プルしたモデルは以下のコマンドで確認することもできます。

```bash
$ docker model list
```

![スクリーンショット 2025-04-10 20.32.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/998e22fa-d84f-4484-9e27-fef65780d246.png)

### 4. `.env`を設定
アプリケーションで読み込む環境設定ファイル`.env`に`BASE_URL`と`MODEL`を追加します。`ENV`は環境切替用の変数です。

```env
ENV=local
BASE_URL=http://model-runner.docker.internal/engines/v1
MODEL=ai/gemma3:1B-Q4_K_M
```

>`docker.internal` は Docker Desktop が提供する特殊な内部ホスト名です。

### 5. OpenAI互換APIとして利用
NestJS(TypeScript)などから以下のように呼び出します。

```ts
axios.post(`${BASE_URL}/chat/completions`, {
  model: MODEL,
  messages: [...]
})
```

OpenAI SDKを使っている場合も、`baseURL`を切り替えるだけでそのまま使えます。

## 実装例(NestJS)：条件分岐によるLLMサービスの環境切替

```ts
const llmResponse =
  process.env.ENV === 'local'
    ? await this.openAIService.queryOpenAI(systemPrompt, userPrompt)
    : await this.bedrockService.queryBedrock(systemPrompt, userPrompt)
```

```ts
@Injectable()
export class OpenAIService {
  constructor(private readonly openAIClient: OpenAIClient) {}

  async queryOpenAI(systemPrompt: string, userPrompt: string): Promise<string> {
    try {
      return await this.openAIClient.sendQuery(systemPrompt, userPrompt)
    } catch (error) {
      const message = error?.message ?? '不明なエラー'
      throw new InternalServerErrorException(`AIモデルからの応答取得に失敗しました: ${message}`)
    }
  }
}
```

```ts
export class OpenAIClient {
  private readonly openai: OpenAI
  private readonly model: string

  constructor() {
    const apiKey = process.env.OPENAI_API_KEY ?? 'dummy-key'
    const baseURL = process.env.BASE_URL ?? 'https://api.openai.com/v1'

    this.model = process.env.MODEL ?? 'gpt-4o-mini'

    this.openai = new OpenAI({
      apiKey,
      baseURL
    })

    if (baseURL.includes('api.openai.com') && !process.env.OPENAI_API_KEY) {
      console.warn('OPENAI_API_KEYが設定されていません。OpenAI APIは利用できません。')
    }
  }

  async sendQuery(systemPrompt: string, userPrompt: string): Promise<string> {
    try {
      const response = await this.openai.chat.completions.create({
        model: this.model,
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: userPrompt }
        ],
        temperature: 0.2,
        max_tokens: 500,
        top_p: 0.5
      })

      const content = response.choices[0]?.message?.content
      if (!content) {
        throw new Error(`${this.model}モデルからの有効な応答を取得できませんでした`)
      }

      return content
    } catch (error) {
      const message = error?.response?.data?.error?.message ?? error?.message ?? '不明なエラー'
      console.error(`${this.model}モデルからの応答取得に失敗しました:`, message)
      throw error
    }
  }
}
```

## docker-composeはそのままでOK！
Docker Model RunnerはDocker Desktopの内部サービスとして動作するため、`docker-compose.yml`に定義を追加する必要はありません。`.env`の設定とモデルの事前pullだけですぐに使えます。

## 今後のロードマップ
Docker社の発表によると、今後は以下の機能が追加される予定みたいです。
- Windows対応(GPU高速化あり)
- カスタムモデルの公開機能
- Composeやテストコンテナとの統合

https://www.docker.com/blog/introducing-docker-model-runner/


## 参考リンク

https://www.docker.com/blog/run-llms-locally/