前回の第9回では、モデルをONNXに変換して速度を比べました。今回は、ここまで作ったAPIをDocker(アプリを、動かす環境ごと「箱(コンテナ)」に詰めて配る技術)で動かせるようにします。
開発者のパソコンでは動くのに、本番のサーバーでは動かない。この定番のトラブルを減らし、運用担当者が同じ手順で起動できるようにするための準備です。
この回で作るもの
- APIを動かす環境の設計図
Dockerfile - 起動・設定をまとめた
compose.yaml(Docker Composeの設定ファイル) - 動いているかを確かめるヘルスチェック
- 個人情報を残さないログの出力
先にお伝えしたいこと:この回の確認範囲
この連載の執筆環境では、Dockerイメージの実際のビルド(作成)と起動までは確認できていません。 外部のイメージ配布元やパッケージ配布元への接続が制限されており、ビルドの途中で止まったためです。確認できたのは次の範囲です。
docker compose configによる、compose.yamlの書き方の検証(エラーなし)- ログ出力の追加と、そのテスト(
pytest33件成功) - Dockerを使わない起動(
uvicorn)での、API・画面の動作(第4〜5回で確認済み)
読者の環境でビルドするときは、初回にうまくいかない可能性があります。その場合は、まず docker compose build のエラーメッセージを確認してください。
仕組み:コンテナに何を入れ、何を外に置くか
| 置き場所 | 何を置くか | 理由 |
|---|---|---|
| コンテナの中(イメージ) | Python・部品・app/ のコード | 誰が動かしても同じ環境になる |
| コンテナの外(読み取り専用で渡す) | AIモデルのファイル(models/) | 差し替えやすく、イメージが軽くなる |
| 実行時の設定(環境変数) | しきい値・モデルのパス | 作り直さずに変えられる |
AIモデルは、コードと更新のタイミングが違います。イメージに焼き込まず、外から渡す形にしておくと、モデルの入れ替えでイメージを作り直さずに済みます。
実装
Dockerfile
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 依存だけ先に入れる(app を変えても、この層のキャッシュが効く)。推論だけなので CPU 版の PyTorch を使う
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu \
# サーバーには画面がないので、画面表示用の部品(libGL など)を必要としない headless 版の OpenCV に差し替える
&& pip uninstall -y opencv-python \
&& pip install --no-cache-dir opencv-python-headless==4.13.0.92
COPY app ./app
# root では動かさない
RUN useradd --create-home appuser
USER appuser
# Ultralytics の設定ファイルの置き場所(書き込める場所を指定)
ENV YOLO_CONFIG_DIR=/tmp/ultralytics \
YOLO_MODEL_PATH=/models/yolo26n.pt
EXPOSE 8000
CMD ["uvicorn", "app.api:app", "--host", "0.0.0.0", "--port", "8000"]
工夫した点は次のとおりです。
- 依存を先に入れる:コードだけを変えたときは、重い部品のインストールをやり直さず、素早く作り直せます
- CPU版のPyTorch:推論だけなら、GPU用の巨大な部品は不要です
- headless版のOpenCV:サーバーには画面がないため、画面表示用の部品が要らない版に差し替えます
- rootで動かさない:万一、APIに問題があっても、コンテナの中での権限を最小限にします
compose.yaml
# compose.yaml
services:
detector:
build: .
ports:
- "127.0.0.1:8000:8000" # 自分のパソコンからだけ接続できる。社内に公開するときは認証とあわせて見直す
volumes:
- ./models:/models:ro # モデルは画像に焼き込まず、外から読み取り専用で渡す(差し替えやすい)
environment:
YOLO_CONFIDENCE: "0.25"
read_only: true # コンテナ内のファイルは書き換えられない
tmpfs:
- /tmp # 一時ファイルだけは書ける
restart: unless-stopped
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s
logging:
driver: json-file
options:
max-size: "10m" # ログでディスクがいっぱいにならないよう、大きさと世代を制限する
max-file: "3"
- ポートは自分のパソコンにだけ公開:
127.0.0.1:8000:8000と書くと、他のパソコンからは接続できません。第4回で触れたとおり、このAPIには認証がないためです read_only: true:コンテナの中のファイルを書き換えられなくします。攻撃を受けても、コードを書き換えにくくなります- ヘルスチェック:30秒ごとに
/healthを呼び、応答がなければ「異常」と記録します。起動直後はモデルの読み込みに時間がかかるため、最初の40秒は判定を待ちます - ログの上限:1ファイル10MB・3世代まで。ログでディスクがいっぱいになり、システムが止まるのを防ぎます
restart: unless-stopped:異常で止まったら、自動で再起動します
ログを残す:ただし個人情報は残さない
運用では、「いつ・何枚・何秒かかったか」が分かるログが欠かせません。app/api.py に、ログの出力を足しました。
# app/api.py(追加部分)
started = time.perf_counter()
try:
detections = request.app.state.detector.detect_bytes(data, conf)
except ValueError as exc:
logger.warning("画像を読み込めませんでした bytes=%d", len(data))
raise HTTPException(status_code=400, detail=str(exc)) from exc
# ファイル名には個人情報が含まれうるので、ログには残さない(大きさ・件数・時間だけ)
logger.info("detect bytes=%d conf=%.2f count=%d ms=%.0f", len(data), conf, len(detections),
(time.perf_counter() - started) * 1000)
ログに残すのは、データの大きさ・しきい値・検出件数・処理時間だけです。ファイル名は残しません。 「田中様_免許証.jpg」のように、個人名などが含まれることがあるためです。テストでも、ファイル名がログに出ないことを確認しています。
動かす手順(読者の環境で)
python tools/download_assets.py # モデルを models/ に用意する(初回のみ)
docker compose up --build # イメージを作って起動
curl http://127.0.0.1:8000/health # {"status":"ok"} が返れば起動している
docker compose ps # STATUS が healthy になれば、ヘルスチェックも通っている
先ほどお伝えしたとおり、この手順は、執筆環境ではビルドまで確認できていません。
つまずきやすい点
- モデルが見つからない:
./modelsにモデルのファイルが無いと、起動に失敗します。先にダウンロードが必要です - イメージが大きい:PyTorchなどを含むため、イメージは数GBになることがあります。ビルドと配布に時間がかかります
- 書き込み禁止でエラー:
read_onlyにすると、書き込みが必要な場所(設定ファイルなど)でエラーになります。書き込める場所(/tmp)を指定しています - CPUの割り当て:コンテナに割り当てるCPUが少ないと、遅くなります。第9回の速度は、割り当てで変わります
発注者向けメモ:「どこで動かすか」で費用と責任が変わる
コンテナ化は、開発者の都合というより、運用の引き継ぎやすさに関わる話です。誰が動かしても同じ環境になる、というのは、開発会社の交代や、社内の担当者への引き継ぎで効いてきます。
発注者がやること チェックリスト
- ☐ どこで動かすか(社内サーバー・クラウド)と、その運用を誰が行うかを決めた
- ☐ 環境の構築手順書(または、コンテナの設計図)が、納品物に含まれていることを確認した
- ☐ ログに、個人情報(ファイル名・画像の中身)が残らない設計か確認した
- ☐ ログの保存期間と、確認する人を決めた
- ☐ 異常時の連絡先と、再起動などの一次対応の手順を決めた
開発会社への質問例
- 「動かす環境を、他の担当者や別の開発会社が再現できる形(コンテナの設計図など)で納品してもらえますか」
- 「ログには何が記録されますか。個人情報や画像の中身が残らないことを、どう確認しますか」
- 「異常を検知する仕組み(ヘルスチェック・通知)は、見積もりに含まれていますか」
- 「モデルを更新するとき、システムを止める時間はどのくらいですか」
まとめと次回予告
第10回では、次のことを行いました。
- Dockerfileとcompose.yamlを作り、環境・設定・ヘルスチェック・ログの上限を整理した
- モデルをコンテナの外から渡す形にして、差し替えやすくした
- ログに、個人情報になりうるファイル名を残さないようにした
- ビルドと起動は、この執筆環境では確認できていないことを明記した
次回の第11回(最終回)では、運用時の注意点(精度の低下・ライセンス・更新)と、この連載の内容の総まとめを行います。
この連載の記事一覧
この記事は連載「YOLOで作る画像認識ツール」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。
- 【YOLOで作る画像認識ツール 第0回】全体像と環境づくり:YOLOで最初の1枚を認識する
- 【YOLOで作る画像認識ツール 第1回】信頼度のしきい値とJSON出力:認識結果を「使える形」にする
- 【YOLOで作る画像認識ツール 第2回】認識結果を画像に描いて確認する:枠とラベルで「間違い」を見つけやすくする
- 【YOLOで作る画像認識ツール 第3回】種類ごとに何個あるか数えて、CSVに集計する
- 【YOLOで作る画像認識ツール 第4回】FastAPIで画像認識を「API」にして、ほかのシステムから使えるようにする
- 【YOLOで作る画像認識ツール 第5回】ブラウザから写真をアップロードして、認識結果をその場で見る
- 【YOLOで作る画像認識ツール 第6回】精度を数字で測る:正解データと、適合率・再現率
- 【YOLOで作る画像認識ツール 第7回】学習済みモデルにない対象を覚えさせる:追加学習の手順と、結果の正しい読み方
- 【YOLOで作る画像認識ツール 第8回】自信の低い結果だけ人が確認する:AIと人の役割分担を設計する
- 【YOLOで作る画像認識ツール 第9回】速く・軽くする:ONNXに変換してCPUで動かし、変換前後を比べる
- 【YOLOで作る画像認識ツール 第10回】Dockerで動かす:設定・ログ・ヘルスチェックを運用に近い形にする(この記事)
- 【YOLOで作る画像認識ツール 第11回(最終回)】運用の注意点とライセンス(AGPL-3.0):精度の低下に気づく仕組みと、連載のまとめ

