この記事の対象読者
- Dockerの基本(イメージ、コンテナ、ボリューム)は理解している方
-
docker runを何度も叩いて「これ毎回やるの面倒...」と思い始めた方 - AI開発でGPU付きの複数サービス(API + DB + 推論エンジン)をまとめて管理したい方
この記事で得られること
-
Composeの本質: 「なぜ
docker runの連打ではダメなのか」が腹落ちする - compose.yamlの読み書き: サービス、ネットワーク、ボリューム、依存関係の定義を完全理解
- 実践力: GPU対応AIスタック、Web+DB+キャッシュ構成、開発ワークフロー自動化まで身につく
この記事で扱わないこと
- Kubernetes(次回記事で扱います)
- Docker Swarmによるマルチノードオーケストレーション
- Compose v5 Go SDKのプログラミング詳細
1. Docker Composeとの出会い
docker runを5個連続で叩いた日、私は悟った。「これは人間がやる仕事じゃない」と。
事の発端は、Ollama(ローカルLLM推論サーバー)+ Open WebUI + PostgreSQL + Redis + Nginx をRTX 5090環境で動かそうとした時のことです。
# 1つ目: ネットワーク作成
docker network create ai-stack
# 2つ目: PostgreSQL
docker run -d --name postgres --network ai-stack -v pgdata:/var/lib/postgresql/data -e POSTGRES_PASSWORD=secret postgres:16
# 3つ目: Redis
docker run -d --name redis --network ai-stack redis:7-alpine
# 4つ目: Ollama(GPU付き)
docker run -d --name ollama --network ai-stack --gpus all -v ollama-models:/root/.ollama ollama/ollama
# 5つ目: Open WebUI
docker run -d --name webui --network ai-stack -p 8080:8080 -e OLLAMA_BASE_URL=http://ollama:11434 ghcr.io/open-webui/open-webui:main
5つのコンテナ、5つのdocker run。さらにこれを止めるにはdocker stopを5回、削除するにはdocker rmを5回。環境を別のマシンに再現するには、このコマンド群をどこかにメモしておく必要がある。そしてメモは必ず古くなる。
Docker Composeは、この「複数コンテナの定義と管理」を1つのYAMLファイルにまとめ、docker compose upの一撃で全てを起動する仕組みです。
料理に例えるなら、docker runの連打は「レシピを口頭で伝える」こと。Docker Composeは「レシピブック」を作ること。誰が見ても同じ料理が再現できる。そしてレシピブックはバージョン管理できる。
ここまでで、Docker Composeの存在意義がなんとなく掴めたでしょうか。次は、この記事で使う用語を整理しておきましょう。
2. 前提知識の確認
本題に入る前に、この記事で頻出する用語を確認します。
2.1 サービス(Service)とは
Composeにおけるサービスは、Dockerコンテナの「定義」です。1つのサービスが1つのコンテナに対応します(スケールすれば複数)。services:ブロック内に記述します。
2.2 プロジェクト(Project)とは
Composeのプロジェクトは、1つのcompose.yamlで定義されたサービス群の総称です。デフォルトではディレクトリ名がプロジェクト名になります。ネットワークやボリュームはプロジェクト単位で隔離されます。
2.3 Compose Specification とは
Docker Composeの設定ファイル形式の仕様です。かつては version: '3.8' のようにバージョンを明記していましたが、2026年現在のCompose v5ではversionフィールドは不要(非推奨)です。仕様はローリングリリースで更新されます。
2.4 プロファイル(Profile)とは
サービスをグループ化する仕組みです。たとえば「開発時だけ使うデバッグツール」や「テスト時だけ使うDBシード」をプロファイルで分けることで、本番環境と開発環境をひとつのcompose.yamlで管理できます。
これらの用語が押さえられたら、Docker Composeの歴史と背景を見ていきましょう。
3. Docker Composeが生まれた背景
3.1 「1コンテナ1プロセス」の原則と現実のギャップ
Dockerには「1コンテナに1つのプロセスを割り当てる」というベストプラクティスがあります。Webアプリ、データベース、キャッシュ、リバースプロキシ...それぞれが独立したコンテナ。
しかし現実のアプリケーションは複数のサービスが連携して動きます。問題は:
| 課題 |
docker runの連打では... |
|---|---|
| 起動順序 | DBが先に起動していないとアプリがクラッシュする |
| ネットワーク | コンテナ間通信のためにネットワークを手動で作る必要がある |
| 再現性 | コマンド群をメモしないと別環境で再現できない |
| 一括操作 | 停止・削除・再起動が個別に必要 |
| 設定の共有 | 環境変数の管理がバラバラ |
3.2 Docker Composeの進化の歴史
2014年 ─ Fig(Figプロジェクト)としてスタート
│ Orchard Laboratories がPython製ツールとして開発
▼
2014年 ─ Docker社がOrchardを買収、Docker Composeに改名(v1)
│ コマンド: docker-compose(ハイフンあり)
│ 設定ファイル: docker-compose.yml
│ ファイルフォーマット: version 1, 2.x, 3.x
▼
2020年 ─ Docker Compose V2 発表
│ Go言語で完全リライト(v1はPython製)
│ コマンド: docker compose(スペース、Dockerのサブコマンドに)
│ Compose Specification を採用
│ version フィールドが任意に
▼
2026年1月 ─ Docker Compose V5 リリース ← 今ここ
Go SDK の公式提供
Docker Desktop 4.56.0 に同梱
※ v3, v4 はレガシーファイルフォーマットとの混同回避で欠番
なぜv3とv4が欠番なのか?: docker-compose.yml の version: '3.8' というファイルフォーマット番号との混乱を避けるため。Compose CLIのバージョンをv5に飛ばして、明確に区別しました。
3.3 Compose V5 の注目ポイント(2026年現在)
| 機能 | 説明 |
|---|---|
| Go SDK | CLIを使わずにGoアプリからComposeを操作可能 |
| Compose Watch | ファイル変更を監視して自動sync/rebuild/restart |
| Docker Model Runner連携 | compose.yamlでLLMモデルを直接定義可能 |
| 時間ベースpull_policy |
daily, weekly, every_Xh でイメージ更新を制御 |
| プロバイダーサービス | 外部システムやクラウドサービスを直接参照 |
| Compose Bridge | Compose定義をKubernetesマニフェストに変換(GA) |
背景がわかったところで、基本的な仕組みを見ていきましょう。
4. 基本概念と仕組み
4.1 compose.yaml の構造
compose.yamlは大きく4つのトップレベル要素で構成されます。
# compose.yaml の全体構造
services: # ← コンテナの定義(必須)
web:
image: nginx
db:
image: postgres
networks: # ← ネットワークの定義(任意)
frontend:
backend:
volumes: # ← 永続ボリュームの定義(任意)
db-data:
configs: # ← 設定ファイルの定義(任意)
secrets: # ← 秘密情報の定義(任意)
┌─────────────────────────────────────────────┐
│ compose.yaml │
│ │
│ ┌─────────── services ───────────┐ │
│ │ web: db: cache: │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │nginx│ │postgres│ │redis│ │ │
│ │ └──┬──┘ └──┬──┘ └──┬──┘ │ │
│ └────┼─────────┼─────────┼─────┘ │
│ │ │ │ │
│ ┌────┴─────────┴─────────┴─────┐ │
│ │ networks │ │
│ │ frontend ←→ backend │ │
│ └──────────────┬────────────────┘ │
│ │ │
│ ┌──────────────┴────────────────┐ │
│ │ volumes │ │
│ │ db-data cache-data │ │
│ └───────────────────────────────┘ │
│ │
│ docker compose up → 全サービス起動 │
│ docker compose down → 全サービス停止+削除 │
└─────────────────────────────────────────────┘
4.2 サービス定義の主要フィールド
services:
myapp:
# --- イメージ指定(2つの方法) ---
image: python:3.12-slim # 既存イメージを使う場合
build: # Dockerfileからビルドする場合
context: .
dockerfile: Dockerfile
# --- ネットワーク ---
ports:
- "8080:8080" # ホスト:コンテナ
networks:
- frontend
# --- データ永続化 ---
volumes:
- ./src:/app/src # バインドマウント(開発用)
- app-data:/app/data # 名前付きボリューム(永続化)
# --- 環境変数 ---
environment:
- DB_HOST=db
- DB_PORT=5432
env_file:
- .env # .envファイルから読み込み
# --- 依存関係 ---
depends_on:
db:
condition: service_healthy # DBのヘルスチェック通過を待つ
# --- リソース制限 ---
deploy:
resources:
limits:
memory: 2g
cpus: "1.0"
reservations:
devices: # GPU予約
- driver: nvidia
count: all
capabilities: [gpu]
# --- ヘルスチェック ---
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
# --- その他 ---
restart: unless-stopped # 異常終了時に自動再起動
shm_size: "4g" # /dev/shm のサイズ
4.3 ネットワークの自動生成
Docker Composeの最も便利な特徴のひとつが、ネットワークの自動管理です。
# 明示的にnetworksを定義しなくても...
services:
web:
image: nginx
db:
image: postgres
この場合、Composeは自動的に<プロジェクト名>_defaultというbridgeネットワークを作成し、全サービスを接続します。そしてサービス名がそのままホスト名になります。
web コンテナ内から:
$ ping db → PostgreSQLに到達する
$ curl web:80 → 自分自身にも名前で到達
DNS解決: サービス名 → コンテナのIPアドレス
明示的にネットワークを分離したい場合:
services:
nginx:
networks: [frontend, backend] # 両方に接続
api:
networks: [backend] # バックエンドのみ
db:
networks: [backend] # バックエンドのみ
networks:
frontend: # 外部向け
backend:
internal: true # 外部からアクセス不可
4.4 depends_on と起動順序制御
depends_onはサービスの起動順序を制御しますが、単純なdepends_onは「コンテナの起動」を待つだけで、「サービスの準備完了」は待ちません。
# ❌ BAD: DBコンテナが起動しただけでアプリが接続を試みる → 失敗
services:
app:
depends_on:
- db
# ✅ GOOD: DBのヘルスチェックが通るまで待つ
services:
app:
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
基本概念が理解できたところで、実際にコードを書いて動かしてみましょう。
5. 実践:実際に使ってみよう
5.1 環境構築
この記事の実践パートは、以下の環境で動作確認しています。
| 項目 | バージョン |
|---|---|
| OS | Windows 11 Pro 24H2 + WSL2 |
| ディストロ | Ubuntu 24.04 LTS |
| Docker Engine | 29.x |
| Docker Compose | v5.1.0 |
| GPU | NVIDIA RTX 5090 |
5.2 環境別の設定ファイル
開発環境用(compose.yaml)
# compose.yaml - 開発環境用
# 起動: docker compose up -d
# 停止: docker compose down
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
volumes:
- ./src:/app/src # ソースコードをバインドマウント(ホットリロード用)
environment:
- ENV=development
- DEBUG=true
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=myapp_dev
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
restart: unless-stopped
develop: # Compose Watch(V5機能)
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: requirements.txt
db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=myapp_dev
- POSTGRES_USER=dev
- POSTGRES_PASSWORD=devpassword
ports:
- "5432:5432" # 開発時はホストからDB直接接続可能に
volumes:
- db-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # 初期化SQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev -d myapp_dev"]
interval: 5s
timeout: 3s
retries: 10
cache:
image: redis:7-alpine
ports:
- "6379:6379" # 開発時はRedis CLIで直接接続可能に
volumes:
- cache-data:/data
volumes:
db-data:
cache-data:
本番環境用(compose.prod.yaml)
# compose.prod.yaml - 本番環境用
# 起動: docker compose -f compose.yaml -f compose.prod.yaml up -d
services:
app:
build:
context: .
dockerfile: Dockerfile.prod
ports:
- "8000:8000"
environment:
- ENV=production
- DEBUG=false
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=${DB_NAME}
- REDIS_URL=redis://cache:6379
env_file:
- .env.prod # 本番用シークレット
volumes: [] # バインドマウント無効化
deploy:
resources:
limits:
memory: 2g
cpus: "2.0"
restart: always
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
ports: [] # 本番ではDB外部公開しない
volumes:
- db-data:/var/lib/postgresql/data
deploy:
resources:
limits:
memory: 1g
cache:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
ports: [] # 本番ではRedis外部公開しない
nginx: # 本番のみリバースプロキシ追加
image: nginx:1.27-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app
restart: always
テスト/CI環境用(compose.test.yaml)
# compose.test.yaml - テスト/CI環境用
# 実行: docker compose -f compose.yaml -f compose.test.yaml run --rm test
services:
app:
environment:
- ENV=test
- DEBUG=false
- DB_HOST=db
- DB_NAME=myapp_test
ports: []
db:
environment:
- POSTGRES_DB=myapp_test
- POSTGRES_USER=test
- POSTGRES_PASSWORD=testpassword
ports: []
tmpfs: # テスト用DBはメモリ上で高速化
- /var/lib/postgresql/data
test: # テスト実行専用サービス
build:
context: .
dockerfile: Dockerfile
command: pytest tests/ -v --cov=src --cov-report=html
environment:
- ENV=test
- DB_HOST=db
- DB_NAME=myapp_test
depends_on:
db:
condition: service_healthy
volumes:
- ./tests:/app/tests
- ./htmlcov:/app/htmlcov # カバレッジレポート出力
5.3 Compose操作の実践スクリプト
#!/usr/bin/env bash
# compose_guide.sh - Docker Compose 操作ガイドスクリプト
# 実行方法: bash compose_guide.sh
set -euo pipefail
echo "=========================================="
echo " Docker Compose 実践ガイド"
echo "=========================================="
echo ""
echo "=== 1. 基本コマンド ==="
cat << 'EOF'
# 全サービスをバックグラウンドで起動
$ docker compose up -d
# 特定サービスだけ起動
$ docker compose up -d db cache
# ログをリアルタイム表示
$ docker compose logs -f
# 特定サービスのログだけ
$ docker compose logs -f app
# サービスの状態確認
$ docker compose ps
# 全サービス停止 + コンテナ削除
$ docker compose down
# ボリュームも含めて完全削除(データ消失注意)
$ docker compose down -v
EOF
echo ""
echo "=== 2. ビルド & 更新 ==="
cat << 'EOF'
# Dockerfileを変更後にリビルド
$ docker compose build
# キャッシュ無効でリビルド(依存関係が壊れた時)
$ docker compose build --no-cache
# リビルド + 起動を一発で
$ docker compose up -d --build
# 特定サービスだけリビルド
$ docker compose build app
# イメージの更新を確認してプル
$ docker compose pull
EOF
echo ""
echo "=== 3. 複数ファイルの合成 ==="
cat << 'EOF'
# 開発環境(デフォルト)
$ docker compose up -d
# 本番環境(compose.yamlを上書き)
$ docker compose -f compose.yaml -f compose.prod.yaml up -d
# テスト実行
$ docker compose -f compose.yaml -f compose.test.yaml run --rm test
EOF
echo ""
echo "=== 4. プロファイル ==="
cat << 'EOF'
# compose.yaml 内のプロファイル定義例:
# services:
# debug-tools:
# profiles: [debug]
# image: busybox
# デフォルト起動(debug-toolsは起動しない)
$ docker compose up -d
# debugプロファイルも含めて起動
$ docker compose --profile debug up -d
# 有効なプロファイル一覧
$ docker compose config --profiles
EOF
echo ""
echo "=== 5. Compose Watch(開発ワークフロー) ==="
cat << 'EOF'
# ファイル変更を監視して自動反映
$ docker compose up --watch
# watchの動作:
# sync → ファイルをコンテナにコピー(ホットリロード向き)
# rebuild → Dockerイメージを再ビルド(依存関係変更時)
# sync+restart → ファイルコピー後にサービス再起動
EOF
echo ""
echo "=== 6. 便利なワンライナー ==="
cat << 'EOF'
# サービス内でコマンド実行
$ docker compose exec app bash
$ docker compose exec db psql -U dev -d myapp_dev
# 一時的なコンテナで実行(使い捨て)
$ docker compose run --rm app python manage.py migrate
# 全サービスのリソース使用量
$ docker compose top
# compose.yamlの設定を解決した結果を表示(デバッグ用)
$ docker compose config
# 特定サービスだけ再起動
$ docker compose restart app
# 特定サービスをスケール
$ docker compose up -d --scale worker=3
EOF
echo ""
echo "=========================================="
echo "✅ ガイド完了!"
5.4 実行結果
開発環境の一括起動:
$ docker compose up -d
[+] Running 4/4
✔ Network myproject_default Created 0.1s
✔ Container myproject-db-1 Healthy 5.3s
✔ Container myproject-cache-1 Started 0.8s
✔ Container myproject-app-1 Started 5.5s
$ docker compose ps
NAME STATUS PORTS
myproject-app-1 Up 10 seconds 0.0.0.0:8000->8000/tcp
myproject-cache-1 Up 15 seconds 0.0.0.0:6379->6379/tcp
myproject-db-1 Up 15 seconds 0.0.0.0:5432->5432/tcp
$ docker compose logs app --tail 5
myproject-app-1 | INFO: Application startup complete.
myproject-app-1 | INFO: Uvicorn running on http://0.0.0.0:8000
myproject-app-1 | INFO: Connected to PostgreSQL at db:5432
myproject-app-1 | INFO: Redis connection established at cache:6379
myproject-app-1 | INFO: Environment: development (DEBUG=true)
5.5 よくあるエラーと対処法
| エラー | 原因 | 対処法 |
|---|---|---|
service "app" depends on "db" which is not healthy |
DBのヘルスチェックがタイムアウト |
healthcheckのretriesとintervalを増やす。start_periodを設定してDB初期化時間を確保 |
port is already allocated |
同じポートが別のCompose/コンテナで使用中 |
docker psで確認。compose.yamlのポートマッピングを変更するか、競合コンテナを停止 |
no matching manifest for linux/amd64 |
イメージがプラットフォーム非対応 |
platform: linux/amd64を明示するか、対応イメージを選択 |
could not select device driver "nvidia" |
NVIDIA Container Toolkit未インストール | NVIDIA Container Toolkitをインストール。nvidia-ctk runtime configure --runtime=dockerを実行後Dockerを再起動 |
bind mount source path does not exist |
バインドマウントのホスト側パスが存在しない | マウント先のディレクトリを事前に作成。相対パスはcompose.yamlのあるディレクトリ基準 |
yaml: line X: did not find expected key |
compose.yamlのインデント崩れ | YAMLはスペース2個のインデント。タブは不可。VS CodeのDocker拡張で自動検証 |
docker compose down -vは永続ボリュームを全削除します。DBデータ、キャッシュデータ、全て消えます。本番環境では-vフラグを絶対につけないでください。開発環境のリセット専用です。
5.6 環境診断スクリプト
#!/usr/bin/env python3
"""
Docker Compose環境診断スクリプト
実行方法: python3 check_compose_env.py
"""
import subprocess
import os
import sys
import json
from pathlib import Path
def run_cmd(cmd: str) -> str:
"""コマンドを実行して結果を返す"""
try:
result = subprocess.run(
cmd, shell=True, capture_output=True, text=True, timeout=15
)
return result.stdout.strip()
except (subprocess.TimeoutExpired, Exception):
return ""
def check_compose_environment():
"""Docker Compose環境を包括的にチェック"""
issues = []
warnings = []
print("=" * 55)
print(" Docker Compose 環境診断レポート")
print("=" * 55)
# --- 1. Compose基本情報 ---
print("\n🐳 Docker Compose基本情報:")
compose_ver = run_cmd("docker compose version --short 2>/dev/null")
if compose_ver:
print(f" Compose: v{compose_ver}")
major = int(compose_ver.split('.')[0]) if compose_ver[0].isdigit() else 0
if major < 2:
issues.append("Compose v1(Python版)は非推奨です。v5へアップグレードしてください。")
else:
issues.append("Docker Composeがインストールされていません。")
return
# Docker Engine
engine_ver = run_cmd("docker version --format '{{.Server.Version}}' 2>/dev/null")
print(f" Docker Engine: {engine_ver or '接続不可'}")
# BuildKit
buildkit = run_cmd("docker buildx version 2>/dev/null")
print(f" BuildKit: {'有効' if buildkit else '要確認'}")
# --- 2. compose.yamlの検出 ---
print("\n📄 Composeファイル検出:")
compose_files = [
"compose.yaml", "compose.yml",
"docker-compose.yaml", "docker-compose.yml"
]
found_files = [f for f in compose_files if Path(f).exists()]
if found_files:
for f in found_files:
print(f" ✅ {f}")
if len(found_files) > 1:
warnings.append(
f"複数のComposeファイルが存在します: {found_files}。"
"docker compose configで優先順位を確認してください。"
)
else:
print(" ℹ️ カレントディレクトリにComposeファイルなし")
# --- 3. compose.yamlの検証 ---
if found_files:
print("\n🔍 Composeファイル検証:")
config_result = run_cmd("docker compose config --quiet 2>&1")
if config_result:
print(f" ⚠️ 検証エラー: {config_result[:200]}")
issues.append("compose.yamlに構文エラーがあります。")
else:
print(" ✅ 構文チェックOK")
# サービス数
services = run_cmd("docker compose config --services 2>/dev/null")
if services:
service_list = services.strip().split('\n')
print(f" サービス数: {len(service_list)}")
for s in service_list:
print(f" - {s}")
# --- 4. 実行中のComposeプロジェクト ---
print("\n📦 実行中のComposeプロジェクト:")
ls_result = run_cmd("docker compose ls 2>/dev/null")
if ls_result:
for line in ls_result.strip().split('\n'):
print(f" {line}")
else:
print(" 実行中のプロジェクトなし")
# --- 5. GPU確認 ---
print("\n🎮 GPU(Compose GPU対応):")
nvidia_check = run_cmd("docker info --format '{{.Runtimes}}' 2>/dev/null")
if "nvidia" in str(nvidia_check).lower():
print(" NVIDIA Runtime: 有効 ✅")
print(" → compose.yamlでdeploy.resources.reservations.devicesを使用可能")
else:
print(" NVIDIA Runtime: 未検出")
print(" → GPU不要なら問題なし")
# --- 6. ディスク使用量 ---
print("\n💾 ディスク使用量:")
df_result = run_cmd(
"docker system df --format 'table {{.Type}}\t{{.Size}}\t{{.Reclaimable}}' 2>/dev/null"
)
if df_result:
for line in df_result.strip().split('\n'):
print(f" {line}")
# --- 7. .envファイル確認 ---
print("\n🔐 環境変数ファイル:")
env_files = [".env", ".env.dev", ".env.prod", ".env.test"]
for ef in env_files:
if Path(ef).exists():
line_count = sum(
1 for line in open(ef) if not line.startswith('#') and line.strip()
)
print(f" ✅ {ef} ({line_count}変数)")
# --- 結果サマリー ---
print("\n" + "=" * 55)
if issues:
print("❌ 問題:")
for issue in issues:
print(f" 🔴 {issue}")
if warnings:
print("⚠️ 注意:")
for w in warnings:
print(f" 🟡 {w}")
if not issues and not warnings:
print("✅ Docker Compose環境は正常です!")
print("=" * 55)
if __name__ == "__main__":
check_compose_environment()
実装方法がわかったので、次は具体的なユースケースを見ていきます。
6. ユースケース別ガイド
6.1 ユースケース1: GPU対応AIローカル推論スタック
想定読者: OllamaやローカルLLMをDocker Composeで管理したい方
推奨構成: Ollama(GPU推論) + Open WebUI + PostgreSQL
サンプルコード:
# compose.ai-stack.yaml - GPU対応AIローカル推論スタック
# 起動: docker compose -f compose.ai-stack.yaml up -d
# UI: http://localhost:8080
services:
# --- LLM推論エンジン ---
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama-models:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all # 全GPUを使用
capabilities: [gpu]
environment:
- NVIDIA_VISIBLE_DEVICES=all
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
# --- WebUI ---
webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
ports:
- "8080:8080"
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- DATABASE_URL=postgresql://aiuser:aipassword@db:5432/openwebui
- WEBUI_AUTH=true
volumes:
- webui-data:/app/backend/data
depends_on:
ollama:
condition: service_healthy
db:
condition: service_healthy
restart: unless-stopped
# --- データベース ---
db:
image: postgres:16-alpine
container_name: ai-postgres
environment:
- POSTGRES_DB=openwebui
- POSTGRES_USER=aiuser
- POSTGRES_PASSWORD=aipassword
volumes:
- ai-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U aiuser -d openwebui"]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped
volumes:
ollama-models: # LLMモデルファイル(数GB〜数十GB)
webui-data: # WebUI設定・チャット履歴
ai-db-data: # PostgreSQLデータ
#!/usr/bin/env bash
# setup_ai_stack.sh - AIスタックの初期セットアップ
# 実行方法: bash setup_ai_stack.sh
set -euo pipefail
COMPOSE_FILE="compose.ai-stack.yaml"
echo "=== AI推論スタック セットアップ ==="
# 1. 起動
echo "[1/3] スタック起動中..."
docker compose -f "$COMPOSE_FILE" up -d
# 2. Ollamaの準備完了を待つ
echo "[2/3] Ollamaの起動を待機中..."
until docker compose -f "$COMPOSE_FILE" exec -T ollama \
curl -sf http://localhost:11434/api/tags > /dev/null 2>&1; do
echo " 待機中..."
sleep 3
done
echo " Ollama起動完了 ✅"
# 3. モデルのダウンロード
echo "[3/3] LLMモデルをダウンロード中..."
docker compose -f "$COMPOSE_FILE" exec -T ollama ollama pull llama3.2:8b
echo ""
echo "=========================================="
echo "✅ セットアップ完了!"
echo " WebUI: http://localhost:8080"
echo " Ollama: http://localhost:11434"
echo ""
echo "GPUの確認:"
docker compose -f "$COMPOSE_FILE" exec ollama \
nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>/dev/null \
|| echo " (GPU情報取得不可)"
echo "=========================================="
6.2 ユースケース2: Web + DB + キャッシュの本番構成
想定読者: Webアプリケーションの開発〜本番デプロイまでをComposeで管理したい方
推奨構成: FastAPI + PostgreSQL + Redis + Nginx
サンプルコード:
# compose.webapp.yaml - Webアプリ本番構成
# 起動: docker compose -f compose.webapp.yaml up -d
services:
# --- リバースプロキシ ---
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
api:
condition: service_healthy
restart: always
# --- APIサーバー ---
api:
build:
context: .
dockerfile: Dockerfile
target: runtime
environment:
- DB_URL=postgresql+asyncpg://appuser:${DB_PASSWORD}@db:5432/webapp
- REDIS_URL=redis://cache:6379/0
- SECRET_KEY=${SECRET_KEY}
expose:
- "8000" # nginxからのみアクセス可能
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 15s
timeout: 5s
retries: 3
deploy:
resources:
limits:
memory: 1g
cpus: "2.0"
restart: always
# --- バックグラウンドワーカー ---
worker:
build:
context: .
dockerfile: Dockerfile
target: runtime
command: python -m celery -A app.worker worker --loglevel=info
environment:
- DB_URL=postgresql+asyncpg://appuser:${DB_PASSWORD}@db:5432/webapp
- REDIS_URL=redis://cache:6379/0
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
deploy:
resources:
limits:
memory: 512m
restart: always
# --- データベース ---
db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=webapp
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d webapp"]
interval: 5s
timeout: 3s
retries: 10
deploy:
resources:
limits:
memory: 512m
restart: always
# --- キャッシュ + メッセージブローカー ---
cache:
image: redis:7-alpine
command: >
redis-server
--appendonly yes
--maxmemory 256mb
--maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
deploy:
resources:
limits:
memory: 300m
restart: always
volumes:
postgres-data:
redis-data:
6.3 ユースケース3: Compose Watchによる開発ワークフロー自動化
想定読者: docker compose up --buildを繰り返すのが面倒な方
推奨構成: Compose V5のWatch機能を活用した自動開発環境
サンプルコード:
# compose.watch-demo.yaml - Compose Watch のデモ
# 起動: docker compose -f compose.watch-demo.yaml up --watch
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
develop:
watch:
# ソースコード変更 → コンテナ内に即座にsync(ホットリロード)
- action: sync
path: ./frontend/src
target: /app/src
ignore:
- node_modules/
- "*.test.ts"
# package.json変更 → イメージを再ビルド
- action: rebuild
path: ./frontend/package.json
backend:
build: ./backend
ports:
- "8000:8000"
develop:
watch:
# Pythonコード変更 → sync + サービス再起動
- action: sync+restart
path: ./backend/src
target: /app/src
# requirements.txt変更 → イメージを再ビルド
- action: rebuild
path: ./backend/requirements.txt
| アクション | 速度 | 用途 | 向いているFW |
|---|---|---|---|
sync |
最速(秒以下) | ファイルをコンテナにコピー | React, Next.js, Vue(HMR対応) |
sync+restart |
速い(数秒) | ファイルコピー + サービス再起動 | Flask, FastAPI(--reload無し) |
rebuild |
遅い(分単位) | イメージを再ビルド | 依存関係変更時(package.json等) |
ユースケースを把握できたところで、この先の学習パスを確認しましょう。
7. 学習ロードマップ
この記事を読んだ後、次のステップとして以下をおすすめします。
初級者向け(まずはここから)
-
既存の
docker runコマンドをcompose.yamlに変換する: 自分のdocker runをservices:ブロックに書き直す練習 - 公式チュートリアル: Docker Compose Getting Started を一通り進める
-
.envファイルで秘密情報を管理する: パスワードをcompose.yamlにハードコードしない習慣をつける
中級者向け(実践に進む)
-
マルチファイル構成:
compose.yaml+compose.prod.yaml+compose.test.yamlの使い分け -
Compose Watch:
docker compose up --watchで開発ワークフローを自動化 -
ヘルスチェック + depends_on条件:
service_healthyで確実な起動順序を実現
上級者向け(さらに深く)
- Compose Bridge: Compose定義からKubernetesマニフェストを生成
- Go SDK: Compose v5のSDKを使ってGoアプリからコンテナを操作
-
セキュリティ:
secrets:、configs:、ネットワーク分離(internal: true)の活用
8. まとめ
この記事では、Docker Composeについて以下を解説しました:
- Composeの本質: 複数コンテナの定義をYAMLに宣言し、一括管理する仕組み
- compose.yamlの構造: services / networks / volumes の3つの柱と、サービス間の依存関係・ヘルスチェックによる起動順序制御
- 環境の分離: 開発/本番/テストをマルチファイル構成で安全に管理する方法
- 実践: GPU対応AIスタック、本番Webアプリ構成、Compose Watchによる開発自動化
私の所感
Docker Composeとの本当の出会いは、RTX 5090でOllama + Open WebUI + PostgreSQL をまとめて動かした時でした。docker runを5個叩いていた時は「まあこんなもんか」と思っていましたが、compose.yamlを書いてdocker compose up -d一発で全部立ち上がった瞬間、「なぜもっと早く使わなかったのか」と後悔しました。
特にGPUパススルーがdeploy.resources.reservations.devicesで宣言的に書けるのが革命的です。docker run --gpus allと違い、compose.yamlに書いてあれば誰がどの環境で起動しても同じGPU設定が適用される。CUDA Gapで散々苦しんだ身としては、「環境の再現性」がいかに重要か身に染みています。
2026年現在、Compose v5はGo SDKの搭載、Docker Model Runnerとの統合、Compose BridgeによるKubernetes連携と、単なる「docker runの便利ラッパー」を遥かに超えた存在に進化しています。
「docker runの連打」から「compose.yamlの1ファイル管理」へ。この移行は、あなたの開発体験を確実に変えるはずです。
参考文献
- Docker Compose Official Documentation — 公式ドキュメント
- Compose Specification — ファイル仕様
- Compose V5 Release Notes — V5リリースノート
- History and development of Docker Compose — Composeの歴史
- Enable GPU support in Compose — GPU対応公式ガイド
- Compose SDK — Go SDK ドキュメント
この記事は「わかったつもりになってない?」シリーズの一部です。
| No. | タイトル | 状態 |
|---|---|---|
| 1 | Linuxってなんだ? | ✅ 公開済み |
| 2 | Ubuntuってなんだ? | ✅ 公開済み |
| 3 | WSLってなんだ? | ✅ 公開済み |
| 4 | Dockerってなんだ? | ✅ 公開済み |
| 5 | Docker Composeってなんだ?(この記事) | ✅ 公開済み |
| 6 | Kubernetesってなんだ? | ✅ 公開済み |
📌 X(Twitter)でも技術情報を発信しています: @geneLab_999