DockerにおけるAstral uv: マルチステージビルド、BuildKitキャッシュ、高速CI

Table of Contents
Pythonコンテナを構築するすべての開発者は、Dockerレイヤーの無効化によるフィードバックループの遅さを経験しています。アプリケーションコードを1行変更し、docker buildを実行すると、PyTorch、Pandas、または重いC拡張機能をPyPIから再ダウンロードするために、ターミナルが4分間も費やすのを目にすることになります。
AstralのRustベースのパッケージマネージャーuvとDocker BuildKitキャッシュマウントを使用すれば、再ビルド時間を2秒未満に短縮できます。
ビルド比較の概要:
| 戦略 | コールドビルド時間 | コードのみの再ビルド | 最終イメージサイズ | CVE攻撃対象領域 |
|---|---|---|---|---|
pip install -r reqs.txt | 約65秒 | 約45秒(無効化された場合) | 約850MB | 高(コンパイラが残る) |
poetry install | 約110秒 | 約60秒 | 約920MB | 高(poetryランタイムが残る) |
| uvマルチステージ + BuildKit | 約4.5秒 | 約1.2秒 | 約140MB | 最小限(純粋な仮想環境) |

従来のPython Dockerfileの3つの欠点
ほとんどのプロダクションDockerfileには、構造的なパフォーマンスの問題が3つあります。
- コンパイラが最終イメージに残る: シングルステージのDockerfile内で
apt-get install gcc build-essentialを実行すると、コンパイラ、ヘッダーファイル、パッケージマネージャーのキャッシュがプロダクションコンテナ内に残り、イメージサイズが1GBを超えて肥大化します。 - レイヤーキャッシュのスラッシング: 依存関係をインストールする前に
COPY . .を配置すると、コミットごとに依存関係キャッシュが無効になります。 - CIでの永続的なホイールキャッシュがない: 新しいGitHub Actionsランナーで
docker buildを実行すると、プルリクエストごとにPyPIからすべてのホイールが再ダウンロードされます。
解決策は、公式のスタンドアロンuvバイナリとBuildKit永続キャッシュマウントを使用したクリーンなマルチステージパイプラインです。
フェーズ1: スタンドアロンのuvバイナリの取得
RUN pip install uvを実行する(Pythonが必要で不要なレイヤーを追加する)代わりに、コンパイル済みのRustバイナリをAstralの公式イメージから直接コピーします。
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim AS builder
# Copy the standalone uv binary directly (sub-millisecond copy)
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
これにより、ビルドオーバーヘッドなしでuvがすぐに利用可能になります。
フェーズ2: 2段階の依存関係同期
ソースコードの編集によって依存関係のダウンロードが再実行されるのを防ぐため、インストールを2つの異なるステージに分割します。
WORKDIR /app
# Enable bytecode compilation and standalone copy linking
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
# Step A: Copy ONLY lockfiles first
COPY pyproject.toml uv.lock ./
# Step B: Install dependencies without installing the root project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --no-install-project
--no-install-projectに注目してください。これはuvに対し、アプリケーションのソースコードがまだ存在しないことを前提として、仮想環境を構築し、すべてのサードパーティの依存関係をダウンロードするように指示します。
次に、ソースコードをコピーして最終同期を実行します。
# Step C: Copy application source code
COPY src/ ./src/
# Step D: Fast sync to install your project into the existing virtualenv
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
src/main.pyを編集すると、DockerはステップBのキャッシュされたレイヤーを再利用します。すべてのホイールがすでに.venvに存在するため、ステップDは300ミリ秒未満で実行されます。
フェーズ3: 軽量なプロダクションランナー
ビルダー段階には、uv、パッケージメタデータ、および中間ビルドツールが含まれています。これらはどれもプロダクションには不要です。
ステージ2では、クリーンなpython:3.12-slimベースから開始し、非ルートユーザーを作成し、.venvディレクトリのみをコピーします。
FROM python:3.12-slim AS runner
# Create non-root system user
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /app
# Copy the pre-built virtual environment from builder
COPY --from=builder --chown=appuser:appgroup /app/.venv /app/.venv
COPY --from=builder --chown=appuser:appgroup /app/src /app/src
# Put virtualenv binaries at the front of PATH
ENV PATH="/app/.venv/bin:$PATH"
USER appuser
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
PATHが/app/.venv/binを指しているため、uvicornを実行すると、python -mやアクティベーションスクリプトを必要とせずに、仮想環境内のバイナリが直接実行されます。
このコンテナ内で実行するASGIスタックを選択する場合は、FastAPI vs Litestar: Performance & Memoryに関するベンチマーク比較をご覧ください。プロダクション負荷の下で非同期ワーカーがOOMクラッシュなしで安定稼働するようにするには、プロダクションでの非同期Pythonメモリリークのプロファイリングに関するガイドと組み合わせてください。
完全なコピペ可能なプロダクションDockerfile
以下は、プロダクション対応の完全で堅牢なマルチステージDockerfileです。
# syntax=docker/dockerfile:1.7
# Stage 1: Build virtual environment
FROM python:3.12-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
# Optimize uv execution inside containers
ENV UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy
# 1. Cache third-party dependencies independently of source code
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --no-install-project
# 2. Copy source code and perform final project sync
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
# Stage 2: Final minimal production runtime
FROM python:3.12-slim AS runner
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser
WORKDIR /app
# Copy only the compiled virtualenv and application source
COPY --from=builder --chown=appuser:appuser /app/.venv /app/.venv
COPY --from=builder --chown=appuser:appuser /app/src /app/src
ENV PATH="/app/.venv/bin:$PATH" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
GitHub ActionsでのBuildKitキャッシュマウントの永続化
ローカルマシンでは、Docker BuildKitはビルド間で/root/.cache/uvを自動的にキャッシュします。一時的なGitHub Actionsランナーでは、ランナーが終了するとそのキャッシュは消滅します。
CI実行間でuvキャッシュを永続化するには、DockerのGitHub Actionsキャッシュバックエンドを設定します(リモートキャッシュタイプとレジストリエクスポーターについては、Docker BuildKitキャッシュマウントとマルチステージ最適化に関する詳細な解説をご覧ください)。
name: Docker Build & Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
cache-to: type=gha,mode=maxを追加することで、BuildKitは永続的な/root/.cache/uvマウントをGitHubのキャッシュストレージにエクスポートします。その後のPRビルドでは、ネットワークに触れることなくキャッシュされたホイールを直接プルします。
経験則
通常のアプリケーション開発中にDockerビルドが依存関係の解決に5秒以上費やしている場合、レイヤーキャッシュの境界が早すぎます。
ロックファイルのみをコピーし、--no-install-projectで同期し、/root/.cache/uvをマウントし、最終的なランナーには.venvディレクトリのみをコピーしてください。
こちらもおすすめ
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Docker BuildKitのキャッシュマウントとマルチステージ最適化(2026年版ガイド)
Go、Node.js、Rustコンテナ向けに、BuildKitのキャッシュマウント、マルチステージターゲット、リモートのS3/レジストリキャッシュバックエンドを使用してDockerビルドを高速化します。
Read more
FastAPIとDockerマルチステージビルド:イメージサイズを70%削減
コンパイラを本番環境にデプロイするのはやめましょう。uvとBuildKitのキャッシュを使ったFastAPIのDockerマルチステージビルドで、イメージサイズを1.2GBから100MB未満に削減し、CIの再ビルドを高速化する方法を紹介します。
Read more
BigQueryとCloud Runによるサーバーレス分析ウェアハウス:GA4ストリームから自動SEOアラートまで
BigQuery、Google Analytics 4、Cloud Runを使って、スキーマモデリング、スケジュールされたSQL変換、アイドルコストゼロ、自動SEOクエリアラートを備えた自動サーバーレス分析ウェアハウスを構築する方法を紹介します。
Read more