Docker Composeで構築するローカルAI統合環境 — Ollama・ComfyUI・Open WebUIの3コンテナ設計
TL;DROllama(LLM推論)・ComfyUI(画像生成)・Open WebUI(フロントエンド)の3コンテナをDocker Composeで一括管理する。GPUの取り合いはdeviceの明示割り当てとOllamaのアンロード設定で回避し、メモリ制限は「設定しないとホストごと巻き込まれる」必須事項として扱う。
ローカルでLLMを動かしたい。画像生成もしたい。どうせならブラウザから使えるUIも欲しい。この3つの欲求を1台のGPUマシンで満たそうとすると、最初にぶつかるのが「環境の汚染」と「リソースの奪い合い」です。PythonのバージョンやCUDAライブラリの依存関係を素のホストOSに直接積み上げていくと、数週間後には誰も触れない環境ができあがります。そこでDocker Composeを使い、Ollama・ComfyUI・Open WebUIの3つをコンテナとして分離しつつ、1つの定義ファイルで一括管理する構成を組みました。
全体構成と役割分担
役割は明確に分かれています。OllamaはLLMの推論サーバーで、ポート11434でAPIを公開します。ComfyUIはStable Diffusion系モデルを動かすノードベースの画像生成環境です。Open WebUIはこれらの手前に立つWebフロントエンドで、ブラウザからチャットUIとしてOllamaを叩けるようにします。コンテナ間の通信はComposeが作るブリッジネットワーク内で完結させ、ホストに公開するポートはOpen WebUIとComfyUIのUIだけに絞ります。Ollamaのポートをホストに公開しない構成にすれば、LAN内の他端末から推論APIを直接叩かれる事故も防げます。
docker-compose.ymlの実例
核となる定義は次のとおりです。NVIDIA Container Toolkitの導入が前提になります。
docker-compose.ymlservices:
ollama:
image: ollama/ollama:latest
volumes:
- ollama_data:/root/.ollama
environment:
- OLLAMA_KEEP_ALIVE=5m
- OLLAMA_MAX_LOADED_MODELS=1
deploy:
resources:
limits:
memory: 16g
reservations:
devices:
- driver: nvidia
device_ids: ["0"]
capabilities: [gpu]
restart: unless-stopped
comfyui:
image: ghcr.io/lecode-official/comfyui-docker:latest
ports:
- "8188:8188"
volumes:
- comfyui_models:/opt/comfyui/models
deploy:
resources:
limits:
memory: 16g
reservations:
devices:
- driver: nvidia
device_ids: ["1"]
capabilities: [gpu]
restart: unless-stopped
open-webui:
image: ghcr.io/open-webui/open-webui:main
ports:
- "3000:8080"
environment:
- OLLAMA_BASE_URL=http://ollama:11434
volumes:
- webui_data:/app/backend/data
depends_on:
- ollama
deploy:
resources:
limits:
memory: 4g
restart: unless-stopped
volumes:
ollama_data:
comfyui_models:
webui_data:
Open WebUIからOllamaへの接続先がhttp://ollama:11434とサービス名で書けるのは、Composeのネットワーク内でDNS解決が効くためです。localhostと書いてしまうとコンテナ自身を指してしまい接続に失敗します。初学者が最初に踏む典型的な罠なので、コンテナのネットワーク名前空間はホストと別物だという点は早めに体で覚えておくべきです。
GPUリソース競合の回避策
GPUが複数枚あるなら、device_idsでコンテナごとに物理GPUを固定割り当てするのが最も確実です。上の例ではOllamaにGPU 0、ComfyUIにGPU 1を割り当てており、互いのVRAMを侵食しません。研究室で使っているGTX 1080 Ti(11GB)の3枚構成でも、この方式で推論と画像生成を同時に走らせられています。
GPUが1枚しかない場合は物理的な分離ができないため、時間的な分離で逃がします。鍵になるのがOllamaのOLLAMA_KEEP_ALIVEです。デフォルトではモデルが5分間VRAMに常駐しますが、これを短くするか、画像生成の前にollama stopで明示的にモデルをアンロードすれば、ComfyUIにVRAMを譲れます。逆にComfyUI側は生成完了後もモデルをVRAMに保持し続けるので、同時利用が前提なら起動オプションでVRAM使用を抑えるモード(--lowvramなど)を検討します。7BクラスのLLMを4bit量子化で載せて約5GB、SDXLの生成で8GB前後を消費するため、11GBのカード1枚での完全同居は実質的に無理がある、というのが実測からの結論です。
メモリ制限は「任意」ではなく「必須」
見落とされがちですが、limits.memoryの指定はこの構成では必須だと考えています。LLMのロードや画像生成の前処理はRAMを大量に消費し、無制限のままだとコンテナの暴走が直接ホストのOOMを引き起こします。最悪の場合、カーネルのOOM KillerがSSHデーモンや別の重要プロセスを道連れにし、リモートからサーバーに入れなくなります。コンテナ単位で上限を切っておけば、被害はそのコンテナの強制終了だけで済み、restart: unless-stoppedが自動で立て直してくれます。障害の影響範囲を設計段階で限定しておくという発想は、クラウドのインフラ設計にもそのまま通じる考え方です。
運用して分かったこと
この構成の利点は、壊してもdocker compose downとup -dで数分で元に戻せることです。モデルデータは名前付きボリュームに永続化されているため、コンテナを作り直してもダウンロードし直しは発生しません。一方で、イメージの更新追従は自動ではないので、docker compose pullを定期的に実行する運用ルールが要ります。ローカルAI環境は流行りのツールを雑に積みがちですが、リソースの上限と境界を最初に決めてから積む。それだけで安定性がまるで違ってきます。