BackEnd

uvの本番環境用dockerのマルチステージビルド

投稿日:

はじめに

uvで本番環境のdockerイメージをビルドするにはどのような方法が良いのか調査したいと思い、今回はFastAPIとMySQLを使ったサンプルアプリケーションを題材に調査してみました。

MySQLドライバには mysqlclient を使います。このドライバはネイティブライブラリに依存していて、例えばDebianではビルド時に pkg-config gcc 、実行時には default-libmysqlclient-dev が必要です。

このような場合、マルチステージビルドを使ったほうが、最終のイメージサイズを小さくすることができるので、今回はそれを実践してみたいと思います。

サンプルプロジェクトの作成

まずはuvを使ったサンプルプロジェクトを作成します。

pyproject.toml では、fastapiとmysqlclientに依存しています。dev dependancyとしてpytestに依存しています。

src/uv_docker/app.py を作成します。こちらはFastAPIのアプリケーションで、mysqlclientを使ってSELECTするサンプルとなっています。

シングルステージビルド

まずは、シングルステージのDockerfileを試してみます。

このイメージのポイント

  • プロダクション向けビルドで推奨されている ENV UV_COMPILE_BYTECODE=1 を指定しています。この環境変数を指定することで、ビルド時にバイトコード (.pycファイル) が生成されるため、起動時間が短縮される傾向にあるそうです。
  • mysqlclient パッケージをインストールするために pkg-configgcc が、ランタイムで動作させるために default-libmysqlclient-dev をインストールしています。

このイメージでも問題なく動作はしますが、 mysqlclient パッケージのインストールのためだけに必要なパッケージが入っているせいで、このイメージのサイズは「725MB」もあります。

また、ランタイムに本来不要なパッケージが含まれているというのも気になります。それでは、次の章でマルチステージ化してみましょう。

uv流のマルチステージビルド

uvのドキュメントに書かれているマルチステージビルド用のサンプルを参考に、先ほどのイメージをマルチステージ化してみました。

このイメージのポイント

  • ビルド時のみ必要な pkg-configgcc はbuilderステージでのみのインストール、ランタイムのみで必要な default-libmysqlclient-dev は最終ステージでのみインストールしています。
  • 1回目の uv sync では pyproject.tomluv.lock のみを参照し、 --no-install-project オプションを付けることで依存パッケージのみをインストールしています。
    これにより、ソースコードに変更が加えられたとしても、依存パッケージをインストールするための RUN uv sync はキャッシュが効き、ビルド時間を短縮することができます。
    さらに、 --mount=type=cache,target=/root/.cache/uv を指定することで、2回目の uv sync 時にキャッシュを共有するため、ビルドが速くなります。
  • 2回目の uv sync は、アプリケーションのソースコードがコピーされた状態で実行します。 1回目のキャッシュを使用しつつ実行されるため、依存パッケージのインストール時間はほとんどかかりません。
    また --no-editable を指定することで、プロジェクト自体も .venv 配下の site-packages 内にインストールされます。こうすることで、アプリケーションに必要なものはすべて .venv 配下に収まるようになります。
  • 最終ステージでは、 builderステージからは .venv 配下のコピーし、ランタイムで必要なパッケージをインストールし、FastAPIを起動します。

このイメージは「423MB」とかなり小さくなりました!

便利オプションの紹介(おまけ)

uv sync --no-editable はプロジェクト内に変更があっても.venv内に反映されないのですが、 --reinstall オプションを付けることで強制的に.venv内に再インストールされ、反映されるようになりました。

さいごに

uv流のDockerfileは、初め見た時はよく分からなかったのですが、一つ一つ紐解いていくと効率の良いDockerfileであることが分かり、同時にこのサンプルをベースに各ステージごとに適切な依存パッケージをインストールすることで、最終イメージを小さくすることができました。
uvを使う時はDockerfileにもひと工夫入れてみましょう!

おすすめ書籍

エキスパートPythonプログラミング 改訂4版 (アスキードワンゴ) Effective Python 第2版 ―Pythonプログラムを改良する90項目 動かして学ぶ!Python FastAPI開発入門

page_footer_responsive




-BackEnd
-, ,

執筆者:

免責事項

このブログは、記事上部に記載のある投稿日時点の一般的な情報を提供するものであり、投資等の勧誘・法的・税務上の助言を提供するものではありません。仮想通貨の投資・損益計算は複雑であり、個々の取引状況や法律の変更によって異なる可能性があります。ブログに記載された情報は参考程度のものであり、特定の状況に基づいた行動の決定には専門家の助言を求めることをお勧めします。当ブログの情報に基づいた行動に関連して生じた損失やリスクについて、筆者は責任を負いかねます。最新の法律や税務情報を確認し、必要に応じて専門家に相談することをお勧めします。


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


関連記事

rails

さらば「rails migrate」、よろしく「ridgepole」

1 はじめに2 Ridgepoleとは3 rails migrateではなく、Ridgepoleを選定した理由4 rails migrateからRidgepoleへの移行手順5 capistrano3 ...

RSpecの個人的Tips集〜その1〜

1 はじめに2 テストコードの実行をスキップする3 共通のテストコードを用意する4 外部APIの返却値をスタブにする5 さいごに はじめに みなさん、テストコードは書かれているでしょうか? 私も極力書 ...

laravel logo

[Laravel]データベースの暗号化について考えてみる

1 はじめに2 環境3 Laravelでの暗号化4 暗号化されたカラムは、そのままではLIKE検索ができない4.1 問題点4.2 解決案1:全件取得してPHP側でがんばって検索する4.3 解決案2:暗 ...

Go言語

GoでStructのAccessorを自動生成する

1 はじめに2 Accessorを自動生成する2.1 基本的な使い方2.2 receiver変数を変更する場合2.3 生成するファイル名を変える場合2.4 排他制御を行い場合3 どのように生成している ...

laravel logo

DeployerでLaravelをデプロイ! 初期設定〜レシピのカスタマイズまで

1 はじめに2 Deployerの導入2.1 前提条件2.2 インストール3 デプロイの設定3.1 デプロイスクリプト3.2 サーバサイドの設定3.3 デプロイコマンド3.4 Deployerのディレ ...

フォロー

blog-page_side_responsive

2025年4月
 12345
6789101112
13141516171819
20212223242526
27282930  

アプリ情報

私たちは無料アプリもリリースしています、ぜひご覧ください。 下記のアイコンから無料でダウンロードできます。