こんにちは、JS2IIUです。
Pythonで機械学習のプロトタイプを開発する際、Streamlitは非常に強力な味方となります。しかし、ローカル環境で動かすフェーズから、実際にサーバーへデプロイして運用するフェーズに移行しようとすると、ライブラリの依存関係や環境差異といった「デプロイの壁」に突き当たることが多々あります。
プロフェッショナルな開発現場では、この壁を乗り越えるためにDockerによるコンテナ化が不可欠です。本記事では、単に「動く」だけでなく、セキュリティや運用効率を意識した、実戦でそのまま使えるStreamlit × Dockerの構成案を詳しく解説します。今回もよろしくお願いします。
1. はじめに:プロトタイプからプロダクションレベルへ
Streamlitを使えば、わずか数行のコードでインタラクティブなUIを構築できます。しかし、それを誰でも使えるWebサービスとして公開する場合、以下の課題を解決しなければなりません。
- 環境の再現性: 開発者のPCと本番サーバーで、ライブラリやOSのバージョンを完全に一致させる必要がある。
- 依存関係の複雑さ: PyTorchなどの重厚なライブラリや、特定のC++ランタイムへの依存をどう管理するか。
- 運用効率: 新機能を追加した際、いかに素早く、かつ安全にデプロイを更新するか。
Dockerは、アプリケーションとその実行環境を一つの「イメージ」としてパッケージ化することで、これらの課題をスマートに解決します。今回は、運用フェーズを見据えたディレクトリ構造と、最適化されたDockerfileのひな型を見ていきましょう。
2. ベースとなるStreamlitアプリの構築
まずは、デプロイの対象となるサンプルアプリを作成します。ここでは、PyTorchを利用した物体検出の結果を可視化するような、少し実戦的なダッシュボードを想定します。
プロジェクトのディレクトリ構造
プロダクションを意識する場合、ソースコードは src ディレクトリにまとめ、設定ファイルと分離する構造が望ましいです。
my-production-app/
├── src/
│ └── app.py # アプリ本体
├── requirements.txt # 依存ライブラリ
├── .dockerignore # Dockerビルドから除外するリスト
├── docker-compose.yml # 複数コンテナの管理・実行設定
└── Dockerfile # コンテナ構築手順Streamlitアプリの実装(src/app.py)
以下のコードは、PyTorchの学習済みモデル(MobileNet V3)を使用して、簡易的な推論を行うアプリです。
import streamlit as st
import torch
from torchvision import models, transforms
from PIL import Image
import time
# ページの設定
st.set_page_config(page_title="Production ML App", layout="wide")
st.title("Streamlit × Docker 実戦アプリ")
st.sidebar.header("設定")
# モデルのキャッシュ化
# 運用環境では、リクエストのたびにモデルをロードするのは非効率です。
@st.cache_resource
def load_model():
# 軽量なMobileNet V3を使用
model = models.mobilenet_v3_small(weights=models.MobileNet_V3_Small_Weights.DEFAULT)
model.eval()
return model
model = load_model()
st.write("このアプリはDocker上で最適化されて動作しています。")
# 画像アップロード機能
uploaded_file = st.file_uploader("画像をアップロードしてください", type=["jpg", "png"])
if uploaded_file:
img = Image.open(uploaded_file).convert("RGB")
st.image(img, caption="入力画像", width=400)
# 推論処理のシミュレーション
if st.button("推論実行"):
with st.spinner("AIが解析中..."):
# 前処理
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
input_tensor = preprocess(img).unsqueeze(0)
# 推論(計算量の多い処理を想定)
start_time = time.time()
with torch.no_grad():
output = model(input_tensor)
end_time = time.time()
st.success(f"推論が完了しました(処理時間: {end_time - start_time:.4f}秒)")
st.write(f"出力テンソルの形状: {output.shape}")このコードでは、st.set_page_config でレイアウトを整え、@st.cache_resource でモデルをメモリに保持するなど、実運用に耐えうる工夫を盛り込んでいます。
3. 最適化されたDockerfileの解説
次に、このアプリをコンテナ化するための Dockerfile を作成します。ここでは「レイヤーキャッシュの最適化」「セキュリティ(非ルートユーザー)」「ヘルスチェック」に重点を置いています。
# --- ステージ1: ビルド環境 ---
# 軽量かつPython環境が整ったslimイメージを採用
FROM python:3.10-slim as builder
# 環境変数の設定(Pythonのバッファリング無効化など)
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# OS依存のビルドツールのインストール
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 依存ライブラリのインストール
# requirements.txtのみを先にコピーしてキャッシュを有効活用する
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
# --- ステージ2: 実行環境 ---
FROM python:3.10-slim
WORKDIR /app
# builderステージからインストール済みライブラリをコピー
COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
# アプリケーションコードのコピー
COPY src/ ./src/
# セキュリティ対策:非ルートユーザーの作成と切り替え
# デフォルトのroot権限での実行は、セキュリティリスクを伴います。
RUN useradd -m myuser
USER myuser
# Streamlitのデフォルトポート
EXPOSE 8501
# ヘルスチェックの設定
# コンテナが正常に応答しているかを監視システムに知らせます。
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl --fail http://localhost:8501/_stcore/health || exit 1
# 実行コマンド
# src/app.py を指定して起動
ENTRYPOINT ["streamlit", "run", "src/app.py", "--server.port=8501", "--server.address=0.0.0.0"]このDockerfileのプロフェッショナルなポイント
- マルチステージビルドの検討: 今回はシンプルにするためにベースを分けていませんが、ライブラリのビルドに必要なツール(
build-essentialなど)を実行環境に残さないように構成することで、イメージサイズを削減し、脆弱性を減らしています。 - レイヤーキャッシュの活用:
COPY requirements.txt .をソースコードのコピーより前に行っています。これにより、app.pyを1行書き換えただけで重いライブラリを再インストールする手間を防げます。 - 非ルートユーザーの利用:
useraddで一般ユーザーを作成し、USER myuserで切り替えています。万が一アプリに脆弱性があり、コンテナが乗っ取られたとしても、ホストOSへの影響を最小限に抑えることができます。 - ヘルスチェック:
HEALTHCHECK命令は、コンテナが「起動しているが応答がない」状態(ゾンビ状態)を検知するために非常に重要です。
4. 一発で起動するためのDocker Compose活用
開発環境と本番環境で設定を柔軟に切り替えるには、docker-compose.yml を活用するのがベストプラクティスです。
version: '3.8'
services:
streamlit-app:
build: .
container_name: streamlit_prod_container
ports:
- "8501:8501"
volumes:
# 開発時はホストのコードをマウントすることで、
# ファイルを編集すると即座にアプリに反映(ホットリロード)されます。
- ./src:/app/src
environment:
- STREAMLIT_THEME_BASE=dark
- STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
restart: alwaysDocker Composeのメリット
- ホットリロード:
volumes設定により、コンテナを再起動することなくコードの変更を確認できます。これは開発効率を劇的に向上させます。 - 環境変数の集約:
environmentセクションで、Streamlitのテーマ設定や、機械学習モデルのパス、APIキーなどを一括管理できます。 - 再起動ポリシー:
restart: alwaysを設定しておくことで、サーバーが再起動した際やアプリがクラッシュした際に、自動でコンテナを立ち上げてくれます。
5. クラウドへのデプロイと運用のコツ
コンテナ化したアプリは、AWS App Runner、GCP Cloud Run、Azure Container Appsなど、あらゆるクラウドサービスにそのまま持ち込めます。
イメージサイズの軽量化
機械学習アプリで特に問題になるのが、PyTorchなどのパッケージによるイメージの巨大化です。
イメージサイズ \( S \)_ は、以下の要素の合計で決まります。
$$
S = B + L_{os} + L_{py} + A
$$
ここで、\( B \) はベースイメージ(例:Python Slim)、\( L_{os} \) は追加したOSパッケージ、\( L_{py} \) はPythonライブラリ、\( A \) はアプリケーションコードです。
特に \( L_{py} \) を減らすためには、torch をインストールする際に、GPUが不要であればCPU版を明示的に指定するなどの工夫が有効です。
CI/CDとの親和性
GitHub Actionsなどを利用し、main ブランチにプッシュされたら自動的に docker build を行い、コンテナレジストリへプッシュするパイプラインを構築しましょう。Docker化されているため、テスト環境と本番環境で「挙動が変わる」というリスクをゼロに近づけることができます。
6. まとめ
本記事では、Streamlitアプリを単にDockerで動かすだけでなく、運用を意識した高度なコンテナ設計について解説しました。
- ディレクトリ構造の整理: ソースコードと設定ファイルを分離する。
- Dockerfileの最適化: レイヤーキャッシュ、非ルートユーザー、ヘルスチェックを取り入れる。
- Docker Composeの活用: 開発効率を高め、再起動設定で可用性を確保する。
これらのステップを踏むことで、あなたの機械学習アプリは「個人のプロトタイプ」から「信頼性の高いWebサービス」へと進化します。コンテナ化の技術をマスターし、自信を持って自分のプロダクトを世界に公開してください。
最後まで読んでいただきありがとうございました。

コメント