Python | DevOps・運用:Dockerfile ベストプラクティス

Python Python
スポンサーリンク
  1. まずゴールを決めよう:「本番でそのまま動かせる、軽くて、安全で、再現性の高いイメージ」
  2. ベースイメージの選び方:とりあえず公式の slim 系から始める
    1. なぜ「python:3.12-slim」がよく出てくるのか
    2. バージョンは「明示的に固定」する
  3. 依存関係のインストール:COPY の順番とキャッシュを意識する
    1. なぜ「先に依存ファイルだけ COPY する」のか
    2. 本番イメージには「本番に必要な依存だけ」を入れる
  4. 実行ユーザーと環境変数:root のまま動かさない、設定はコードに埋めない
    1. root ではなく「非特権ユーザー」で動かす
    2. 設定値は環境変数で渡す(Dockerfile にベタ書きしない)
  5. マルチステージビルド:ビルド用と本番用を分けて、イメージを軽くする
    1. 「ビルドにだけ必要なもの」を本番から消す
  6. ログとシグナル:コンテナらしい動かし方を意識する
    1. ログはファイルではなく「標準出力」に出す
    2. PID 1 とシグナルの扱い(プロセスを正しく終了させる)
  7. 開発用と本番用を分ける:Dockerfile を増やすか、引数で切り替えるか
    1. 開発用は「便利さ」、本番用は「安全性と軽さ」
  8. 初心者向け「これだけは守る Dockerfile ベストプラクティス」
  9. まとめ(Dockerfile ベストプラクティスは「軽く・安全で・再現性の高いコンテナを作るための“書き方の型”」)

まずゴールを決めよう:「本番でそのまま動かせる、軽くて、安全で、再現性の高いイメージ」

Dockerfile のベストプラクティスは、一言でいうと
「どこでビルドしても、同じように動く、無駄が少なくて、安全なコンテナを作る書き方」です。

Python だと特に大事なのは次のあたりです。

ベースイメージの選び方
依存関係のインストールの仕方
キャッシュを効かせる COPY / RUN の順番
本番用と開発用の違い(不要なものを入れない)
環境変数やユーザー権限の扱い

これを、具体例を交えながら、かみ砕いて話していきます。


ベースイメージの選び方:とりあえず公式の slim 系から始める

なぜ「python:3.12-slim」がよく出てくるのか

Python の Dockerfile でよく見るのがこれです。

FROM python:3.12-slim
Dockerfile

理由はシンプルで、

公式イメージなので安心
フルイメージより軽い(不要なツールが少ない)
でも最低限の Linux 環境はある

というバランスが良いからです。

いきなり alpine に行くのは、初心者には少しハードルが高いです。
ビルドに必要なライブラリが足りなかったり、musl libc 由来のハマりポイントが出てきたりします。

まずは python:3.12-slim のような「公式の slim 系」で慣れるのが、現実的で安全なスタートです。

バージョンは「明示的に固定」する

もう一つ大事なのは、「タグをちゃんと固定する」ことです。

悪い例はこれです。

FROM python:3
Dockerfile

これだと、「いつの間にか 3.11 から 3.12 に変わっていた」みたいなことが起きます。
CI で突然テストが落ちたり、本番だけ挙動が変わったりして、地味に事故の元です。

できるだけ、

FROM python:3.12-slim
Dockerfile

のように、「メジャー+マイナー」まで固定しておくのがベストプラクティスです。


依存関係のインストール:COPY の順番とキャッシュを意識する

なぜ「先に依存ファイルだけ COPY する」のか

よく見るパターンはこうです。

FROM python:3.12-slim

WORKDIR /app

COPY pyproject.toml poetry.lock ./
RUN pip install -U pip && pip install poetry && poetry install --no-dev

COPY app ./app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Dockerfile

ポイントは、「依存ファイル(pyproject.toml / poetry.lock)だけ先に COPY している」ことです。

Docker のビルドはレイヤーキャッシュを使います。

COPY pyproject.toml poetry.lock ./
RUN poetry install

この二つは、「依存ファイルが変わらない限り、キャッシュが効く」ようになります。
つまり、アプリのコード(app/)をちょこちょこ変えても、毎回依存インストールをやり直さなくて済むわけです。

もし最初から全部 COPY してしまうと、

COPY . .
RUN poetry install
Dockerfile

コードを1行変えるたびに、依存インストールがやり直しになります。
ビルド時間が無駄に伸びて、開発体験が悪くなります。

「依存ファイルだけ先に COPY → install → アプリ本体を COPY」
これは Dockerfile の超重要パターンです。

本番イメージには「本番に必要な依存だけ」を入れる

Poetry や pip の extras を使っている場合、
本番イメージには dev 依存を入れないのがベストです。

例えば、

RUN poetry install --no-dev
Dockerfile

とすることで、pytest や開発用ツールを本番イメージから省けます。

イメージが軽くなる
攻撃面が減る(余計なツールが入っていない)

というメリットがあります。


実行ユーザーと環境変数:root のまま動かさない、設定はコードに埋めない

root ではなく「非特権ユーザー」で動かす

デフォルトの Docker コンテナは root ユーザーで動きます。
でも、本番運用では root のままアプリを動かすのは避けるのがベストプラクティスです。

Dockerfile でユーザーを作って切り替えます。

FROM python:3.12-slim

RUN useradd -m appuser

WORKDIR /app
USER appuser

# ここから先のファイルは appuser の権限で扱われる
Dockerfile

これで、コンテナ内でアプリが乗っ取られても、
root 権限で好き放題されるリスクを減らせます。

「コンテナだから安全」というわけではなく、
コンテナの中でも「最小権限の原則」を守るのがベストプラクティスです。

設定値は環境変数で渡す(Dockerfile にベタ書きしない)

よくあるアンチパターンがこれです。

ENV DATABASE_URL=postgres://user:pass@db:5432/app
ENV SECRET_KEY=super-secret
Dockerfile

これは絶対にやめた方がいいです。

Dockerfile はリポジトリに入ります。
つまり、秘密情報が Git に乗ることになります。

ベストプラクティスは、

Dockerfile では「キーの名前」だけ決める
値は docker compose やデプロイ環境(GitHub Actions の secrets など)から渡す

という形です。

ENV APP_ENV=production
Dockerfile

のような「秘密ではない設定」は書いても構いませんが、
パスワードやトークンは必ず外から注入します。


マルチステージビルド:ビルド用と本番用を分けて、イメージを軽くする

「ビルドにだけ必要なもの」を本番から消す

Python でも、C拡張を含むライブラリを使うときなどは、
ビルドにコンパイラやヘッダファイルが必要になります。

その場合、マルチステージビルドが効きます。

FROM python:3.12-slim AS builder

WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN apt-get update && apt-get install -y build-essential
RUN pip install -U pip && pip install poetry && poetry install --no-dev

COPY app ./app
RUN pytest  # ここでテストしてもよい

FROM python:3.12-slim AS runtime

WORKDIR /app
COPY --from=builder /app /app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Dockerfile

ここでやっていることは、

builder ステージでビルドに必要なもの(コンパイラなど)を入れる
runtime ステージには、ビルド済みの成果物だけをコピーする

という分離です。

これにより、

本番イメージからコンパイラやビルドツールを消せる
イメージサイズが小さくなる
攻撃面が減る

というメリットがあります。

最初からマルチステージを完璧に使いこなす必要はありませんが、
「ビルド用と本番用を分けられる」という発想は、覚えておくと一気にレベルが上がります。


ログとシグナル:コンテナらしい動かし方を意識する

ログはファイルではなく「標準出力」に出す

Docker コンテナでは、ログは基本的に標準出力(stdout)に出します。
アプリ側でファイルに書こうとすると、コンテナのライフサイクルとズレて扱いづらくなります。

FastAPI / Uvicorn なら、デフォルトで標準出力にログが出ます。
Python の logging も、ハンドラを標準出力に向けておくのがベストです。

コンテナオーケストレーション(Docker / Kubernetes / ECS など)は、
標準出力のログを集約してくれる前提で設計されています。

PID 1 とシグナルの扱い(プロセスを正しく終了させる)

Docker コンテナの中で動くプロセスは、PID 1 になります。
PID 1 はシグナルの扱いが少し特殊で、
正しく SIGTERM を受け取って終了できるようにしておく必要があります。

Uvicorn や Gunicorn は、基本的に SIGTERM を受け取ってきれいに終了してくれます。
Dockerfile の CMD で、シェル経由ではなく「exec 形式」で書くのがベストです。

悪い例:

CMD uvicorn app.main:app --host 0.0.0.0 --port 8000
Dockerfile

良い例:

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Dockerfile

後者は、Uvicorn が PID 1 になり、シグナルを正しく受け取れます。


開発用と本番用を分ける:Dockerfile を増やすか、引数で切り替えるか

開発用は「便利さ」、本番用は「安全性と軽さ」

開発中は、こんなものが欲しくなります。

ホットリロード(--reload
デバッガ
テストツール一式

本番では、これらは不要ですし、むしろ邪魔です。

ベストプラクティスとしては、

本番用 Dockerfile(Dockerfile
開発用 Dockerfile(Dockerfile.dev

を分けるか、
ARG でモードを切り替えるか、のどちらかです。

例えば、開発用はこう。

FROM python:3.12-slim

WORKDIR /app
COPY . .
RUN pip install -U pip && pip install -e .[dev]

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
Dockerfile

本番用は、さっきまで話してきたように、

依存だけ先に COPY
dev 依存は入れない
--reload は使わない

という形にします。

「開発の便利さ」と「本番の堅牢さ」は、Dockerfile のレベルで分けてしまう方が、結果的にシンプルです。


初心者向け「これだけは守る Dockerfile ベストプラクティス」

ここまでかなり話したので、最初に意識してほしいポイントだけ、ぎゅっと絞ります。

ベースイメージは python:3.12-slim のように公式 slim 系+バージョン固定にする
依存ファイル(pyproject.toml / requirements.txt)だけ先に COPY してから install する
本番イメージには dev 依存を入れない
秘密情報(DB パスワードなど)は Dockerfile に書かず、環境変数で外から渡す
CMD は exec 形式(配列形式)で書く
可能なら非 root ユーザーで動かす

このあたりを守るだけで、「とりあえず動く Dockerfile」から「本番に耐えうる Dockerfile」に一段階進めます。


まとめ(Dockerfile ベストプラクティスは「軽く・安全で・再現性の高いコンテナを作るための“書き方の型”」)

初心者目線で整理すると、Python の Dockerfile ベストプラクティスはこういうものです。

公式の slim 系イメージをベースにし、依存ファイルだけ先に COPY してキャッシュを効かせ、本番イメージには本番に必要なものだけを入れ、設定は環境変数で外から渡し、できれば非 root ユーザーで動かす。
マルチステージビルドや exec 形式 CMD、ログの標準出力出力などを組み合わせることで、「どこでビルドしても同じように動く、軽くて安全なコンテナ」が手に入る。

タイトルとURLをコピーしました