2026年2月22日
インフラ
CUDA環境構築のベストプラクティス | ベアメタル・Docker・VMの構造比較と最適な選び方
GPU を使った AI 推論や LLM の稼働環境を構築する際、「とりあえず Docker で」と考えるエンジニアは多い。しかし CUDA と Docker の組み合わせは、CPU ベースのアプリケーションとは根本的に異なる課題を抱えている。この記事では、CUDA 環境を Docker で構築する際にハマりやすいポイントを整理し、ベアメタル・Docker・VM それぞれの構造的な違いと、ユースケース別の最適解を解説する。

Docker と CUDA の相性が悪い根本的な理由
Docker の設計思想は「ハードウェアを抽象化し、どこでも同じように動く」こと。一方、CUDA は「特定の NVIDIA GPU ハードウェアに密結合し、ドライバのバージョンまで一致が必要」という性質を持つ。
この2つは本質的に矛盾している。
Docker のポータビリティが GPU で崩れる
通常の Docker コンテナでは、アプリケーションの依存関係をすべてイメージ内に閉じ込めることで環境差異をなくす。しかし GPU を使う場合、ホスト OS の NVIDIA ドライバに依存するため、この「閉じ込め」が成立しない。
通常の Docker: コンテナ内に全依存関係 → どの環境でも動く ✅ GPU Docker: コンテナ内の CUDA ← ホストの NVIDIA ドライバに依存 → 環境ごとに動作が変わる ❌
結果として、ホスト側のドライババージョンとコンテナ内の CUDA バージョンの互換性を常に意識する必要があり、Docker の最大のメリットであるポータビリティが大きく損なわれる。
CUDA × Docker でハマる 5 つのポイント
1. nvidia-container-toolkit の設定
Docker コンテナから GPU にアクセスするには、nvidia-container-toolkit(旧 nvidia-docker2)が必要になる。この「橋渡し」ツールが必要な時点で、Docker 単体では GPU を扱えないことを意味している。
# インストール後、daemon.json の設定が必要 { "runtimes": { "nvidia": { "path": "/usr/bin/nvidia-container-runtime", "runtimeArgs": [] } } }
この設定を忘れると could not select device driver エラーが発生する。Docker 初心者が最初にハマるポイントだ。
2. CUDA バージョンとドライバの互換性
NVIDIA ドライバには「Forward Compatibility」という仕組みがあるが、すべての組み合わせで動くわけではない。
| ホストドライバ | 対応 CUDA | よくあるトラブル |
|---|---|---|
| 535.x | CUDA 12.2 まで | CUDA 12.3 以降のイメージが動かない |
| 545.x | CUDA 12.3 まで | 最新の PyTorch イメージでエラー |
| 550.x+ | CUDA 12.4+ | 比較的安定 |
ドライバをアップデートすればいいが、カーネルモジュールの再ビルドが必要な場合もあり、本番環境では気軽にできない。
3. cgroup v2 問題
Ubuntu 22.04 以降はデフォルトで cgroup v2 を使用する。古いバージョンの Docker や nvidia-container-toolkit では cgroup v2 のデバイス管理に対応しておらず、コンテナから GPU が見えないという問題が発生する。
# コンテナ内で nvidia-smi が動かない $ docker run --gpus all nvidia/cuda:12.2-base nvidia-smi Error: ...
解決には Docker と toolkit の両方を最新版にアップデートする必要がある。
4. 権限とセキュリティ
コンテナから GPU デバイス(/dev/nvidia*)にアクセスするには、適切な権限設定が必要。--privileged フラグを使えば動くが、コンテナのセキュリティ分離が無意味になる。
# これは動くが、セキュリティ的に問題あり docker run --privileged nvidia/cuda:12.2-base nvidia-smi # 正しいやり方 docker run --gpus all nvidia/cuda:12.2-base nvidia-smi
NVIDIA Developer Forum では、2025 年になっても「sudo でしか GPU にアクセスできない」という報告が上がっている。
5. パフォーマンスオーバーヘッド
Docker 自体のオーバーヘッドは通常小さいが、GPU パススルーの場合はデバイスマッピングのレイヤーが入る。特に以下のケースで影響が出やすい。
- メモリ管理: コンテナの memory limit と GPU メモリの管理が別系統
- I/O: モデルファイルの読み込みで、ボリュームマウント経由のレイテンシが加わる
- マルチ GPU: 複数 GPU の割り当てとスケジューリングが複雑化
ベアメタル・Docker・VM の構造比較
3 つの実行環境がどのようにGPUにアクセスするか、構造的な違いを見てみよう。
ベアメタル(物理サーバー直接実行)
┌─────────────────────┐ │ アプリケーション │ │ (CUDA / PyTorch) │ ├─────────────────────┤ │ CUDA Toolkit │ ├─────────────────────┤ │ NVIDIA ドライバ │ ├─────────────────────┤ │ OS (Linux) │ ├─────────────────────┤ │ GPU ハードウェア │ └─────────────────────┘
特徴:
- GPU へのアクセスが最短経路。オーバーヘッドなし
- CUDA とドライバの組み合わせをホストで一度設定すれば安定
- 環境の再現性は手動管理(Ansible 等で対応可能)
Docker(コンテナ実行)
┌─────────────────────┐ │ アプリケーション │ │ (CUDA / PyTorch) │ ├─────────────────────┤ │ CUDA Toolkit │ ← コンテナ内 │ (コンテナイメージ) │ ├─────────────────────┤ │ nvidia-container- │ ← 橋渡しレイヤー │ toolkit │ ├─────────────────────┤ │ Docker Engine │ ├─────────────────────┤ │ NVIDIA ドライバ │ ← ホスト側に依存 ├─────────────────────┤ │ OS (Linux) │ ├─────────────────────┤ │ GPU ハードウェア │ └─────────────────────┘
特徴:
- nvidia-container-toolkit という追加レイヤーが必須
- コンテナ内の CUDA とホストのドライバの互換性を常に管理
- 複数の CUDA バージョンを共存させたい場合には有効
VM(仮想マシン)
┌─────────────────────┐ │ アプリケーション │ │ (CUDA / PyTorch) │ ├─────────────────────┤ │ CUDA Toolkit │ ├─────────────────────┤ │ NVIDIA ドライバ │ ← ゲスト OS 用ドライバ ├─────────────────────┤ │ ゲスト OS │ ├─────────────────────┤ │ ハイパーバイザー │ ← 仮想化レイヤー │ (GPU パススルー │ │ / vGPU) │ ├─────────────────────┤ │ ホスト OS │ ├─────────────────────┤ │ GPU ハードウェア │ └─────────────────────┘
特徴:
- GPU パススルー(SR-IOV / vGPU)の設定が複雑
- ハイパーバイザーによるオーバーヘッドが最も大きい
- マルチテナントで GPU を分割共有する場合に使われる(クラウド事業者向け)
ユースケース別の最適解
単一 GPU × 1 台のマシン → ベアメタル推奨
小型 PC やワークステーション 1 台で LLM 推論を動かすケースでは、Docker のメリットがほぼない。
| 観点 | ベアメタル | Docker |
|---|---|---|
| GPU 性能 | 100%(オーバーヘッドなし) | 95〜99%(デバイスマッピング経由) |
| セットアップ | CUDA インストールのみ | CUDA + Docker + nvidia-container-toolkit |
| トラブル時 | 原因が1箇所 | ホスト/コンテナの切り分けが必要 |
| 運用コスト | OS のアップデートのみ | Docker + toolkit のバージョン管理も必要 |
具体例: NVIDIA GB10 や ASUS NUC に Ollama を入れて社内 AI を稼働させるケース。ベアメタルなら curl -fsSL https://ollama.com/install.sh | sh 一発で CUDA が自動検出される。Docker 経由だと nvidia-container-toolkit のセットアップから始まる。
複数バージョンの CUDA を使い分けたい → Docker が有効
研究開発で PyTorch の異なるバージョン(CUDA 11.8 / 12.1 / 12.4)を切り替えたい場合、Docker は価値がある。
# CUDA 11.8 環境 docker run --gpus all pytorch/pytorch:2.0.1-cuda11.8-cudnn8-runtime # CUDA 12.4 環境 docker run --gpus all pytorch/pytorch:2.5.0-cuda12.4-cudnn9-runtime
ただし、これはあくまで開発・検証環境の話であり、本番の推論サーバーでは1つの CUDA バージョンに固定するのが普通だ。
複数ノードでスケールアウト → Kubernetes(K8s)
GPU サーバーが複数台あり、ワークロードを動的に振り分けたい場合は Kubernetes + NVIDIA GPU Operator が事実上の標準になる。
# GPU リソースのリクエスト例 resources: limits: nvidia.com/gpu:1
ただし、K8s の運用には専任のインフラエンジニアが必要になる。
| 規模 | 最適解 | 必要なスキル |
|---|---|---|
| GPU 1台 | ベアメタル | Linux 基礎のみ |
| GPU 数台(同一用途) | ベアメタル + Ansible | Linux + 構成管理 |
| GPU 数台〜数十台(混在ワークロード) | K8s + GPU Operator | K8s + MLOps |
| 大規模クラスタ(100+) | Slurm / Ray | HPC 専門知識 |
ベアメタルでの構築手順:Ollama 編
小型 PC × 単一 GPU で LLM を稼働させる最もシンプルな構成を紹介する。
1. NVIDIA ドライバのインストール
# Ubuntu の場合 sudo apt update sudo apt install -y nvidia-driver-550 sudo reboot # 確認 nvidia-smi
2. Ollama のインストール
curl -fsSL https://ollama.com/install.sh | sh
Ollama は CUDA を自動検出するため、追加の設定は不要。
3. モデルの取得と起動
# 7B モデル ollama pull llama3.1 # 70B モデル(GB10 など大容量メモリ環境向け) ollama pull llama3.1:70b # API サーバーとして起動(デフォルト: localhost:11434) ollama serve
4. LAN 内からのアクセス設定
# 外部からのアクセスを許可 OLLAMA_HOST=0.0.0.0 ollama serve
これだけで社内ネットワークから AI 推論 API にアクセスできる。Docker なら docker-compose.yml の作成、ボリュームマウント、ポートマッピング、nvidia-runtime の指定…と、はるかに多くの手順が必要になる。
ベアメタルでの構築手順:vLLM 編(uv 使用)
Ollama はシンプルさが強みだが、本格的な推論サーバーが必要な場合は vLLM が選択肢に入る。OpenAI 互換の API サーバーを高スループットで動かせるのが特徴だ。
ここでは Python のパッケージマネージャ uv を使ったセットアップ手順を紹介する。pip + venv よりも高速で、依存関係の解決も安定している。
1. uv のインストール
curl -LsSf https://astral.sh/uv/install.sh | sh
2. vLLM プロジェクトの作成
# プロジェクトディレクトリを作成 mkdir vllm-server && cd vllm-server # Python 3.11 で仮想環境を作成(vLLM の推奨バージョン) uv venv --python 3.11 source .venv/bin/activate # vLLM をインストール(CUDA 対応版が自動選択される) uv pip install vllm
uv は pip の 10〜100 倍高速にパッケージを解決・インストールする。vLLM のような依存関係が多いパッケージで特に差が出る。
3. モデルのダウンロードと起動
# OpenAI 互換 API サーバーとして起動 vllm serve meta-llama/Llama-3.1-8B-Instruct \ --host 0.0.0.0 \ --port 8000 \ --max-model-len 4096
Hugging Face からモデルが自動ダウンロードされ、CUDA を検出して GPU 上で推論が始まる。
4. API の動作確認
curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "meta-llama/Llama-3.1-8B-Instruct", "messages": [{"role": "user", "content": "こんにちは"}], "max_tokens": 256 }'
OpenAI SDK からもそのまま接続できるため、既存のアプリケーションの接続先を変えるだけでローカル LLM に切り替えられる。
Ollama vs vLLM:どちらを選ぶか
| 観点 | Ollama | vLLM |
|---|---|---|
| セットアップ | ワンライナー | uv + 数コマンド |
| 対応モデル | Ollama ライブラリから選択 | Hugging Face 上の全モデル |
| API 互換性 | 独自 + OpenAI 互換 | OpenAI 完全互換 |
| スループット | 単一リクエスト向き | 高並列処理(Continuous Batching) |
| メモリ効率 | 標準 | PagedAttention で最適化 |
| ユースケース | 個人〜小規模チーム | 高負荷の本番推論サーバー |
小規模で手軽に始めるなら Ollama、高スループットが必要な本番環境なら vLLM という使い分けが現実的だ。どちらもベアメタルで動かすことで、Docker のオーバーヘッドとトラブルを回避できる。
まとめ
| 判断基準 | ベアメタル | Docker | K8s |
|---|---|---|---|
| GPU 1台で推論 | ◎ | △ | ✕(過剰) |
| CUDA バージョン切替 | △ | ◎ | ◎ |
| 本番推論サーバー | ◎ | ○ | ○ |
| マルチノード管理 | △ | ○ | ◎ |
| 非エンジニアの運用 | ◎ | ✕ | ✕ |
| トラブル時の切り分け | 簡単 | 中程度 | 複雑 |
「とりあえず Docker」は GPU 環境では正解ではないかもしれない。 単一マシンで LLM を動かすなら、ベアメタルがもっともシンプルで、もっともパフォーマンスが出ます。Docker が本当に必要になるのは、複数の CUDA バージョンを切り替える開発環境か、K8s でオーケストレーションするマルチノード構成の時が多い様に思う。自社の GPU 環境が 1 台で完結するなら、余計な抽象化レイヤーを挟まずベアメタルで動かすことを推奨します。