FastAPIをDockerコンテナで動かそう!


FastAPIをDockerコンテナで動かそう! コンテナ化によるモダンなアプリケーション開発・デプロイ入門

はじめに

現代のWebアプリケーション開発において、スピードと信頼性は最も重要な要素の一つです。Pythonの世界では、非同期処理を活かした高性能なWebフレームワークとしてFastAPIが注目を集めています。同時に、開発環境と本番環境の差異をなくし、アプリケーションのポータビリティを高める技術としてDockerが広く普及しています。

FastAPIで構築した素晴らしいアプリケーションも、開発環境で完璧に動作しても、いざ他の環境(ステージング、本番、あるいは他の開発者のマシン)に移行しようとすると、「自分の環境では動いたのに!」といった問題に直面することは少なくありません。このような「環境依存性」の問題を解決するのがDockerです。

この記事では、FastAPIで作成したシンプルなアプリケーションを題材に、Dockerを使ってコンテナ化し、実行するまでの手順を、初心者の方にも分かりやすく、かつ詳細に解説します。Dockerfileの書き方、イメージのビルド、コンテナの実行といった基本から、より実践的な.dockerignoreの活用、マルチステージビルドによるイメージサイズの最適化、そして複数のサービス(FastAPIアプリケーションとデータベースなど)をまとめて管理できるDocker Composeの使い方までを網羅します。

開発効率の向上、デプロイの信頼性確保、そしてスケーラビリティの実現のために、FastAPIとDockerの組み合わせ方をマスターしましょう。

目次

  1. FastAPIとは? 現代のWebフレームワーク事情
  2. Dockerとは? なぜアプリケーション開発に必要か
  3. FastAPIとDockerを組み合わせるメリット
  4. シンプルなFastAPIアプリケーションの準備
  5. Dockerイメージ構築のレシピ:Dockerfile
    • Dockerfileの基本構造
    • ベースイメージの選択
    • 作業ディレクトリの設定 (WORKDIR)
    • 依存関係ファイルのコピー (COPY requirements.txt)
    • Pythonパッケージのインストール (RUN pip install)
    • アプリケーションコードのコピー (COPY . .)
    • ポートの公開 (EXPOSE)
    • コンテナ起動コマンドの指定 (CMD)
    • 完全なDockerfile (初版)
  6. Dockerイメージのビルド:docker build
  7. Dockerコンテナの実行:docker run
  8. Dockerfileの改善:より実践的なコンテナ化
    • .dockerignoreによる不要ファイルの除外
    • root以外のユーザーで実行するセキュリティ対策
    • マルチステージビルドによるイメージサイズの最適化
    • 環境変数を利用する
  9. Docker Compose:複数のサービスをまとめて管理
    • なぜDocker Composeを使うのか
    • docker-compose.ymlファイルの作成
    • FastAPIサービスの定義
    • 開発時のホットリロードのためのVolume設定
    • Docker Composeでのビルドと実行
  10. Docker Composeでデータベースを連携する
    • PostgreSQLサービスの追加
    • 環境変数の設定とデータベース接続
    • 永続化のためのVolume設定
    • FastAPIアプリケーションからのデータベース接続 (概念説明)
  11. 環境変数の管理と.envファイル
  12. 本番環境へのデプロイを考える (概要)
  13. トラブルシューティング:よくある問題とその解決策
  14. まとめ:FastAPIとDockerで未来へ

1. FastAPIとは? 現代のWebフレームワーク事情

FastAPIは、Pythonのモダンで高速(高性能)、高信頼性のWebフレームワークです。Python 3.6以降で利用できる標準の型ヒントに基づいており、自動的にデータ検証、シリアライズ、そしてインタラクティブなAPIドキュメント(Swagger UIやReDoc)を生成します。

その高速性の秘密は、Starlette(軽量なASGIフレームワーク)とPydantic(データ検証ライブラリ)をベースにしている点にあります。非同期処理(async def, await)を自然に記述できるため、I/Oバウンドな処理が多いWebアプリケーションにおいて、従来のフレームワーク(例: Flask, Djangoの同期処理部分)と比較して高いスループットを発揮できます。

FastAPIは、API開発に特化しており、主にバックエンドREST APIの構築に用いられます。開発者は、強力な型システムによる補完やエラーチェックの恩恵を受けながら、効率的に堅牢なAPIを開発できます。

2. Dockerとは? なぜアプリケーション開発に必要か

Dockerは、アプリケーションとその依存関係を一つの「コンテナ」と呼ばれる軽量でポータブルな実行可能ユニットにパッケージ化するためのプラットフォームです。コンテナは、ホストOSから分離された独立した環境でアプリケーションを実行できます。

なぜDockerが必要なのでしょうか?従来の開発・デプロイでは、以下のような課題がありました。

  • 環境依存性: アプリケーションが必要とする特定のライブラリ、データベースバージョン、OSの設定などが、開発者のローカルマシンとサーバーで異なる場合、予期しないエラーが発生しやすい。
  • 「私の環境では動く」問題: 開発者のマシンでは正常に動作するのに、他の環境では動かないという問題が頻繁に発生する。
  • 依存関係の管理: プロジェクトごとに異なるライブラリのバージョンが必要な場合、システム全体を汚染したり、バージョン競合を引き起こしたりする可能性がある。
  • 開発環境構築の手間: 新しい開発者がプロジェクトに参加する際、必要なミドルウェアやツールをインストールし、設定するのに時間がかかる。
  • スケーリングの困難さ: アプリケーションをスケールさせるために、新しいサーバーに同じ環境を再現し、アプリケーションをデプロイする作業が煩雑。

Dockerを使うことで、これらの課題が解決されます。

  • 一貫性: コンテナはどこでも同じように実行されます。開発、テスト、ステージング、本番環境を通じて、アプリケーションは全く同じ環境で動作します。
  • 分離: 各コンテナは互いに分離されています。一つのコンテナの変更が他のコンテナやホストシステムに影響を与えることはありません。
  • ポータビリティ: コンテナイメージは、Dockerがインストールされているあらゆる場所に簡単に移動させ、実行できます。
  • 効率的なデプロイ: アプリケーションはコンテナイメージとしてパッケージ化されるため、デプロイが迅速かつ信頼性が高くなります。
  • 容易なスケーリング: 同じコンテナイメージから複数のコンテナインスタンスを起動することで、簡単にアプリケーションをスケールできます。

Dockerの主要な概念には「Dockerfile」「Dockerイメージ」「Dockerコンテナ」があります。

  • Dockerfile: イメージを構築するための手順を記述したテキストファイルです。アプリケーションコードのコピー、依存関係のインストール、環境変数の設定、起動コマンドの指定などを行います。
  • Dockerイメージ: Dockerfileの手順に従って構築される、アプリケーションとその実行に必要な全てのものが含まれた読み取り専用のテンプレートです。これを基にコンテナが作成されます。例えるなら、プログラムの「クラス」やOSの「ISOファイル」のようなものです。
  • Dockerコンテナ: Dockerイメージから作成される、実際にアプリケーションが実行される実行中のインスタンスです。例えるなら、クラスから生成される「オブジェクト」やISOファイルからインストールされた「OS」のようなものです。コンテナは起動、停止、削除が容易に行えます。

3. FastAPIとDockerを組み合わせるメリット

FastAPIで開発したアプリケーションをDockerコンテナで実行することには、以下のような多くのメリットがあります。

  1. 環境の統一と再現性:

    • 「開発環境では動いたのに本番環境では動かない」という問題を根本的に解消できます。
    • チーム内の開発者全員が全く同じ環境で開発・テストを行えます。
    • CI/CDパイプラインでのテストやデプロイが信頼性の高いものになります。
  2. 依存関係の分離:

    • システム全体にPythonパッケージやライブラリをインストールする必要がありません。各アプリケーションはそれぞれのコンテナ内で必要な依存関係を持ちます。
    • 異なるFastAPIプロジェクトで異なるPythonバージョンやライブラリバージョンが必要な場合でも、衝突を気にすることなく管理できます。
  3. 開発環境構築の簡素化:

    • 新しい開発者がプロジェクトに参加した際、Dockerをインストールし、リポジトリをクローンしてdocker builddocker compose upを実行するだけで、すぐに開発を開始できます。ローカル環境を汚染することもありません。
  4. デプロイの容易さと信頼性:

    • 一度ビルドしたDockerイメージは、Dockerが動作するどんな環境にもそのままデプロイできます。
    • AWS ECS, Google Kubernetes Engine, Azure Kubernetes Serviceなどのコンテナオーケストレーションプラットフォームと非常に親和性が高く、スケーラブルで回復力の高いシステムを構築するための基盤となります。
  5. リソースの効率的な利用:

    • コンテナは仮想マシンに比べてオーバーヘッドが少なく、リソース(CPU, メモリ)を効率的に利用できます。
  6. 技術スタックの明確化:

    • Dockerfileを見るだけで、そのアプリケーションがどのPythonバージョンを使い、どのような依存関係を持ち、どのように起動するのかが一目でわかります。

FastAPIの持つ「開発のしやすさ、高速性」というメリットを最大限に活かしつつ、Dockerによって「どこでも安定して動く、運用しやすい」アプリケーションとして提供できる、非常に強力な組み合わせと言えます。

4. シンプルなFastAPIアプリケーションの準備

まず、コンテナ化する対象となるFastAPIアプリケーションを準備します。ここでは、非常にシンプルなAPIを作成します。

プロジェクトのディレクトリを作成し、その中に以下のファイルを作成します。

bash
mkdir fastapi-docker-example
cd fastapi-docker-example

main.py:

“`python
from fastapi import FastAPI

FastAPIアプリケーションのインスタンスを作成

app = FastAPI()

ルートエンドポイントの定義

@app.get(“/”)
def read_root():
return {“Hello”: “World”}

/items/{item_id} エンドポイントの定義

@app.get(“/items/{item_id}”)
def read_item(item_id: int, q: str | None = None):
return {“item_id”: item_id, “q”: q}

FastAPIアプリケーションを起動するための準備

通常、FastAPIはUvicornなどのASGIサーバーで実行されます

Dockerfile内でUvicornを使ってこのアプリケーションを起動します

このファイル自体を直接実行するわけではありませんが、

開発時には uvicorn main:app --reload のように実行します

“`

このアプリケーションは、ルートURL (/) で {"Hello": "World"} を返し、/items/{item_id} でパスパラメータとクエリパラメータを受け取って返すだけのシンプルなものです。

requirements.txt:

アプリケーションが依存するPythonパッケージをリストアップします。FastAPIアプリケーションを実行するには、fastapiパッケージと、ASGIサーバーであるuvicornが必要です。

txt
fastapi==0.111.0
uvicorn==0.30.1

(バージョン番号は執筆時点のものです。最新安定版や特定のバージョンを指定してください。)

これで、コンテナ化するための最小限のFastAPIアプリケーションの準備ができました。プロジェクトディレクトリの構造は以下のようになります。

fastapi-docker-example/
├── main.py
└── requirements.txt

5. Dockerイメージ構築のレシピ:Dockerfile

Dockerイメージは、アプリケーションを実行するために必要な全ての要素(コード、ライブラリ、環境設定など)を含む、読み取り専用のテンプレートです。このイメージをどのように構築するかを記述するのがDockerfileです。Dockerfileは、特定の構文に従って記述された一連の命令からなります。

プロジェクトのルートディレクトリ (fastapi-docker-example/) に Dockerfile という名前のファイルを作成します。

Dockerfile (初版):

“`dockerfile

STEP 1: ベースイメージの選択

最小限のPython環境を持つ公式イメージを使用

FROM python:3.9-slim

STEP 2: 作業ディレクトリの設定

これ以降のRUN, CMD, COPYなどのコマンドは、このディレクトリ内で行われます

WORKDIR /app

STEP 3: 依存関係ファイルをコピー

requirements.txtだけを先にコピーすることで、依存関係が変わらない限り

pip installの層のキャッシュが再利用され、ビルドが高速化されます

COPY requirements.txt .

STEP 4: Pythonパッケージのインストール

requirements.txtに記載されたパッケージをインストール

–no-cache-dir はイメージサイズを削減するため

-r はファイルを指定

RUN pip install –no-cache-dir -r requirements.txt

STEP 5: アプリケーションコードをコピー

プロジェクトディレクトリの全てのファイル(Dockerfile, main.pyなど)を

コンテナ内の/appディレクトリにコピーします

COPY . .

STEP 6: ポートの公開

このコンテナがリッスンするポート(FastAPI/Uvicornが実行されるポート)を宣言します

これはドキュメンテーションのためであり、実際には -p オプションでポートマッピングが必要です

EXPOSE 80

STEP 7: コンテナ起動コマンドの指定

コンテナが起動したときに実行されるコマンドを指定します

Uvicornを使ってmain.pyのFastAPIアプリケーション(app)を起動

–host 0.0.0.0 は、コンテナ外からのアクセスを受け付けるために必要

–port 80 は、DockerfileでEXPOSEしたポートと一致させる

CMD [“uvicorn”, “main:app”, “–host”, “0.0.0.0”, “–port”, “80”]
“`

各命令について詳しく見ていきましょう。

Dockerfileの基本構造

Dockerfileは上から順に実行される一連の命令で構成されます。各命令は新しい「レイヤー」を作成します。このレイヤー構造がDockerのキャッシュシステムを支え、効率的なビルドを可能にしています。

ベースイメージの選択 (FROM)

dockerfile
FROM python:3.9-slim

全てのDockerfileはFROM命令から始まります。これは、新しく構築するイメージの基盤となる親イメージを指定します。ここでは、Python 3.9がプリインストールされた公式イメージpython:3.9-slimを使用しています。

  • python: Docker Hubにある公式のPythonイメージです。
  • 3.9-slim: 使用するPythonのバージョン(3.9)と、イメージのタグ(slim)を指定します。slimタグは、完全なOS環境ではなく、Pythonを実行するのに最低限必要なものだけが含まれているため、イメージサイズが小さくなり推奨されます。

作業ディレクトリの設定 (WORKDIR)

dockerfile
WORKDIR /app

この命令は、RUN, CMD, ENTRYPOINT, COPY, ADDなどの後続の命令が実行される際の作業ディレクトリを設定します。コンテナ内でファイル操作を行う際の基準ディレクトリとなります。明示的に設定することで、コマンドが実行される場所を明確にし、Dockerfileの可読性と移植性を高めます。

依存関係ファイルのコピー (COPY requirements.txt)

dockerfile
COPY requirements.txt .

COPY命令は、ホストマシン上のファイルやディレクトリを、イメージ内の指定されたパスにコピーします。

ここでは、ホストマシンの現在のディレクトリにあるrequirements.txtを、イメージ内の現在の作業ディレクトリ(/appWORKDIRで設定したもの)にコピーしています。.は現在の作業ディレクトリを意味します。

なぜrequirements.txtだけを先にコピーするのでしょうか? Dockerは各命令の結果をキャッシュします。もしrequirements.txtが変更されなければ、その後のRUN pip install命令は前回のビルド時のキャッシュを使用できます。もしアプリケーションコード全体を先にコピーしてからrequirements.txtをコピーすると、アプリケーションコードのわずかな変更でもrequirements.txtの層より前に来るため、キャッシュが無効になり、毎回pip installが実行されてしまいます。依存関係はアプリケーションコード本体よりも変更頻度が低いことが多いため、このように分離してコピーすることでビルド時間を短縮できます。

Pythonパッケージのインストール (RUN pip install)

dockerfile
RUN pip install --no-cache-dir -r requirements.txt

RUN命令は、イメージのビルド中にコンテナ内でコマンドを実行します。ここで、前のステップでコピーしたrequirements.txtを基に、必要なPythonパッケージをインストールしています。

  • pip install: Pythonのパッケージインストーラー。
  • --no-cache-dir: pipのキャッシュディレクトリを使用しません。これにより、イメージサイズをさらに削減できます。
  • -r requirements.txt: requirements.txtファイルに記載されたパッケージをインストールします。

アプリケーションコードのコピー (COPY . .)

dockerfile
COPY . .

requirements.txtのインストールが終わったら、残りのアプリケーションコード(main.pyやその他のファイル)をホストマシンの現在のディレクトリからイメージ内の作業ディレクトリ(/app)にコピーします。これで、イメージ内にアプリケーションコードと依存関係が全て揃いました。

ポートの公開 (EXPOSE)

dockerfile
EXPOSE 80

EXPOSE命令は、そのコンテナが特定のポートでリッスンすることを宣言します。これは、あくまでコンテナのユーザーに対するドキュメンテーションとしての役割が主です。コンテナ外からこのポートにアクセスするためには、docker runコマンドでポートマッピングを別途行う必要があります(例: -p 8000:80)。ここでは、Uvicornがポート80で動作するように設定するため、80番ポートを公開しています。

コンテナ起動コマンドの指定 (CMD)

dockerfile
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

CMD命令は、コンテナが起動したときに実行されるデフォルトのコマンドを指定します。Dockerfileには複数のCMDがあっても最後のものだけが有効になります。

ここでは、ASGIサーバーであるUvicornを使って、main.pyファイル内のappという名前のFastAPIアプリケーションを起動しています。

  • uvicorn: Uvicornサーバーを起動するコマンド。
  • main:app: main.pyファイル内のappという名前のFastAPIインスタンスを指定。
  • --host 0.0.0.0: これが重要です。コンテナ内では、localhost(または127.0.0.1)にバインドすると、そのコンテナ自身の中からしかアクセスできません。外部(ホストマシンや他のコンテナ)からアクセスを受け付けるためには、0.0.0.0にバインドする必要があります。
  • --port 80: Uvicornをポート80で起動します。これはEXPOSEで宣言したポートと一致させておくと分かりやすいです。

CMDは配列形式(Exec Form)で記述するのが推奨されます。これにより、シェルを介さずに直接コマンドが実行され、シグナル処理などが正しく行われます。

6. Dockerイメージのビルド:docker build

Dockerfileが完成したら、それを使ってDockerイメージをビルドします。プロジェクトディレクトリ(fastapi-docker-example/)で以下のコマンドを実行します。

bash
docker build -t fastapi-app .

  • docker build: イメージをビルドするためのコマンドです。
  • -t fastapi-app: ビルドするイメージにタグ(名前)を付けます。ここではfastapi-appという名前を付けています。タグ名は任意ですが、分かりやすい名前を付けましょう。バージョン情報を含める場合はfastapi-app:1.0のようにコロンを使って指定します。
  • .: Dockerfileが存在するディレクトリのパスを指定します。.は現在のディレクトリを意味します。Dockerはこのディレクトリとそのサブディレクトリ内のファイルをイメージにコピーできるようになります(.dockerignoreで除外されない限り)。

コマンドを実行すると、DockerはDockerfileを読み込み、記述された手順を上から順に実行していきます。各ステップ(FROM, WORKDIR, COPY, RUNなど)の実行結果が表示されます。初回ビルド時はインターネットからベースイメージのダウンロードやパッケージのインストールが行われるため少し時間がかかる場合があります。2回目以降のビルドでDockerfileに変更がない部分があれば、キャッシュが使用されるため高速に完了します。

ビルドが成功すると、「Successfully built …」のようなメッセージが表示されます。

ビルドされたイメージは以下のコマンドで確認できます。

bash
docker images

fastapi-appという名前のイメージがリストに表示されるはずです。

7. Dockerコンテナの実行:docker run

ビルドしたDockerイメージから、FastAPIアプリケーションを実行するコンテナを作成し、起動します。

bash
docker run -d -p 8000:80 fastapi-app

  • docker run: イメージからコンテナを作成して起動するためのコマンドです。
  • -d: デタッチモード(Detached mode)でコンテナを実行します。これにより、コンテナはバックグラウンドで実行され、ターミナルは解放されます。-dを付けない場合、コンテナの標準出力がターミナルに表示され、コンテナを停止するまでターミナルが占有されます。
  • -p 8000:80: ポートマッピングを指定します。これは「ホストOSのポート8000番へのアクセスを、コンテナ内のポート80番に転送する」という意味です。左側がホスト側のポート、右側がコンテナ側のポートです。DockerfileでEXPOSE 80と指定しましたが、コンテナ外からアクセスするにはこの-pオプションが必須です。ホスト側のポート番号は任意で空いているポートを指定できます(例: 8000, 8080など)。
  • fastapi-app: 実行するDockerイメージの名前(タグ)を指定します。

コマンド実行後、コンテナIDが表示されれば、コンテナはバックグラウンドで起動しています。

コンテナが正常に起動しているか確認するには、以下のコマンドを使います。

bash
docker ps

実行中のコンテナのリストが表示され、fastapi-appイメージから作成されたコンテナが含まれているはずです。STATUSUp ...となっていれば正常に起動しています。

もしコンテナがすぐに停止してしまう場合(docker psで表示されない、またはdocker ps -aExited (...)と表示される)、エラーが発生しています。その場合は、コンテナのログを確認しましょう。コンテナIDはdocker ps -aで確認できます。

bash
docker logs <コンテナID>

ログを確認して、エラーメッセージから原因を特定します(例: Pythonの実行時エラー、Uvicornの起動失敗など)。

コンテナが正常に起動したら、ホストマシンからWebブラウザまたはcurlなどのツールでFastAPIアプリケーションにアクセスしてみましょう。

  • ルートエンドポイント: http://localhost:8000/ にアクセス。{"Hello":"World"} が返ってくるはずです。
  • アイテムエンドポイント: http://localhost:8000/items/5?q=somequery にアクセス。{"item_id":5,"q":"somequery"} が返ってくるはずです。
  • APIドキュメント (Swagger UI): http://localhost:8000/docs にアクセス。FastAPIが自動生成したインタラクティブなAPIドキュメントが表示されます。
  • APIドキュメント (ReDoc): http://localhost:8000/redoc にアクセス。別のスタイルのAPIドキュメントが表示されます。

コンテナを停止するには、docker psでコンテナIDを確認し、以下のコマンドを実行します。

bash
docker stop <コンテナID>

コンテナを削除するには、停止した後に以下のコマンドを実行します。

bash
docker rm <コンテナID>

停止・削除をまとめて行う場合は、docker ps -qで実行中のコンテナIDを取得し、それを引数に使うと便利です。

bash
docker stop $(docker ps -q) # 全ての実行中コンテナを停止
docker rm $(docker ps -a -q) # 全てのコンテナを削除 (-a で停止中のものも含む)

8. Dockerfileの改善:より実践的なコンテナ化

これまでのDockerfileは基本的なものでしたが、より実践的な用途や本番環境での利用を考慮すると、いくつかの改善点があります。

.dockerignoreによる不要ファイルの除外

Dockerイメージに不要なファイルを含めないことは、イメージサイズを小さく保ち、ビルド時間を短縮し、セキュリティリスクを減らす上で重要です。例えば、Pythonの仮想環境ディレクトリ(.venv/), キャッシュファイル(.pyc), Gitのメタデータ(.git/), IDEの設定ファイル、テスト結果などはイメージに含める必要がありません。

プロジェクトのルートディレクトリに.dockerignoreというファイルを作成し、以下のように記述します。

txt
.venv/
__pycache__/
*.pyc
*.so
.git/
.gitignore
.vscode/
temp/
dist/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.env

このファイルにリストされたファイルやディレクトリは、COPY . .などの命令でホストマシンからイメージへコピーされる際に無視されます。gitvenvのようなローカル開発環境に固有のファイルを含めないようにしましょう。

root以外のユーザーで実行するセキュリティ対策

デフォルトでは、Dockerfile内のRUNやコンテナ実行時のプロセスはrootユーザーとして実行されます。これはセキュリティ上のリスクとなります。コンテナを侵害された場合、攻撃者がホストシステム上でroot権限を取得する足がかりになる可能性があります。

より安全な運用のためには、コンテナ内のアプリケーションをroot以外のユーザーで実行することが推奨されます。

Dockerfileにユーザー作成と切り替えのステップを追加します。

“`dockerfile

… (FROM, WORKDIR, COPY requirements.txt, RUN pip install まで同じ)

root以外のユーザーを作成

1000は一般的な非rootユーザーのUIDです

RUN adduser –system –group appuser –uid 1000

作業ディレクトリの所有者を新しく作成したユーザーに変更

これにより、後続のCOPY命令がroot権限なしでファイルを書き込めるようになります

RUN chown -R appuser:appuser /app

アプリケーションコードをコピー (所有者変更後)

COPY . .

以降の命令をこのユーザーで実行

USER appuser

… (EXPOSE, CMD は同じ)

EXPOSE 80
CMD [“uvicorn”, “main:app”, “–host”, “0.0.0.0”, “–port”, “80”]
“`

  • adduser --system --group appuser --uid 1000: appuserという名前のシステムユーザーとグループを作成します。システムユーザーはログインシェルを持たないため、通常のユーザーより権限が制限されます。--uid 1000は特定のユーザーIDを指定します(ホスト側のユーザーIDと合わせるとボリュームマウント時の権限問題が起きにくい場合がありますが、必須ではありません)。
  • chown -R appuser:appuser /app: 作業ディレクトリ/appとその内容の所有者を、作成したappuserに変更します。COPY命令はデフォルトではroot権限で実行されるため、この時点での所有者はrootです。後でUSER appuserに切り替えた際に、アプリケーションが/app内のファイルにアクセス(書き込み含む)できるように所有者を変更します。
  • USER appuser: これ以降の命令(特にCMDENTRYPOINT)は、appuserとして実行されます。アプリケーションプロセスはroot権限を持たなくなります。

この変更により、コンテナのセキュリティが向上します。

マルチステージビルドによるイメージサイズの最適化

一部のPythonパッケージ(例: データベースドライバー、データサイエンス系ライブラリ)は、ビルド時にコンパイラや開発ライブラリを必要とします。これらのビルドツールは、最終的なアプリケーションの実行時には不要ですが、通常のDockerfileでインストールするとそのままイメージに含まれてしまい、イメージサイズが大きくなります。

マルチステージビルドを使用すると、ビルドのために必要なツールを含む「ビルドステージ」と、アプリケーションの実行に必要な最小限のものだけを含む「実行ステージ」を分けることができます。ビルドステージで依存関係のコンパイルなどを行い、その結果物だけを実行ステージにコピーすることで、最終イメージを大幅に小さくできます。

FastAPIアプリケーションは、依存関係に複雑なビルドが必要なライブラリ(例: psycopg2-binary以外)が少なければ、python:x.x-slimのようなイメージで十分小さくできることが多いですが、ここではマルチステージビルドの概念を理解するために例を示します。

例えば、Rustで記述された高速なJSONライブラリorjsonをインストールする場合、ビルド時にRustコンパイラが必要になることがあります。

“`dockerfile

requirements.txt に orjson を追加

fastapi==0.111.0

uvicorn==0.30.1

orjson==3.10.3 # orjsonを追加

“`

単純なDockerfileでは、python:3.9-slimイメージにorjsonをインストールしようとすると、ビルドエラーになるか、あるいはpython:3.9のように完全なイメージを使う必要があり、サイズが大きくなります。

マルチステージビルドの例:

“`dockerfile

— Build Stage —

ビルドに必要なツールを含むイメージ (例: 完全版のPythonイメージや、開発ツールが入ったalpine/debianイメージ)

FROM python:3.9 as builder

WORKDIR /app

ビルドに必要なシステム依存関係をインストール (例: orjsonが必要とするかもしれないもの)

これはあくまで例であり、orjsonの場合は不要かもしれません

RUN apt-get update && apt-get install -y –no-install-recommends build-essential

COPY requirements.txt .

パッケージをインストール –no-cache-dir と –compile を指定することが重要

–compile はコンパイル済みの .pyc ファイルを生成し、実行ステージにコピーする

RUN pip wheel –no-cache-dir –wheel-dir /wheels -r requirements.txt

— Run Stage —

実行に必要な最小限のイメージ (slim版などが適している)

FROM python:3.9-slim as runner

root以外のユーザーで実行する場合 (前のセクションを参照)

RUN adduser –system –group appuser –uid 1000
RUN chown -R appuser:appuser /app
WORKDIR /app # WORKDIRも実行ステージで設定

ビルドステージでインストールしたパッケージ (wheelファイル) をコピー

RUN –mount=type=cache,… pip install … を使う方法もある

COPY –from=builder /wheels /wheels
RUN pip install –no-cache-dir /wheels/* && rm -rf /wheels

アプリケーションコードをコピー

.dockerignore がここで効く

COPY . .

ユーザー切り替え

USER appuser

ポート公開と起動コマンド

EXPOSE 80
CMD [“uvicorn”, “main:app”, “–host”, “0.0.0.0”, “–port”, “80”]
“`

解説:

  1. 最初のFROM python:3.9 as builderで、「builder」という名前のビルドステージを定義します。
  2. このステージで、必要なシステムライブラリをインストールしたり、pip wheelを使ってPythonパッケージをwheelファイルとしてコンパイル・ダウンロードしたりします。pip wheelは依存パッケージを.whlファイルとして指定したディレクトリに保存します。
  3. 二つ目のFROM python:3.9-slim as runnerで、「runner」という名前の実行ステージを定義します。こちらは軽量なイメージをベースとします。
  4. COPY --from=builder /wheels /wheels命令がマルチステージビルドの核心です。これは、他のステージ(builderステージ)の指定されたパス(/wheels)から、現在のステージ(runnerステージ)の指定されたパス(/wheels)にファイルをコピーします。これにより、ビルドステージで生成された必要なアーティファクト(ここではwheelファイル)だけを実行ステージに持ち込めます。
  5. 実行ステージで、コピーしたwheelファイルを使ってパッケージをインストールします。その後、wheelファイルは削除してイメージサイズを削減します。
  6. 残りのステップ(コードコピー、ユーザー切り替え、CMDなど)を実行ステージで行います。

この方法により、コンパイラやビルドツールは最初のbuilderステージにのみ存在し、最終的なrunnerイメージには含まれないため、イメージサイズを劇的に小さくすることができます。

環境変数を利用する

アプリケーションの設定(データベース接続情報、APIキーなど)は、コードに直接書き込むのではなく、環境変数として外部から注入するのがベストプラクティスです。Dockerは環境変数の管理を容易にします。

FastAPIアプリケーションで環境変数を使うには、pydantic-settingsライブラリなどを使うのが便利です。

requirements.txtpydantic-settingsを追加します。

“`txt

pydantic-settings==2.3.3
“`

main.pyを修正して環境変数を読み込むようにします(例としてデータベースURL)。

“`python
from fastapi import FastAPI
from pydantic_settings import BaseSettings, SettingsConfigDict

環境変数を読み込むためのSettingsクラス

class Settings(BaseSettings):
database_url: str = “postgresql://user:password@localhost:5432/mydatabase” # デフォルト値

model_config = SettingsConfigDict(env_file=".env") # .envファイルからの読み込みを許可

settings = Settings()

app = FastAPI()

@app.get(“/”)
def read_root():
# 環境変数から読み込んだ設定を使用
return {“Hello”: “World”, “database_url”: settings.database_url}

… 他のルートは省略

“`

Dockerfileで環境変数を設定する方法はいくつかあります。

  1. Dockerfile内でENV命令を使う: 静的な値を設定する場合。

    “`dockerfile
    FROM python:3.9-slim

    ENV DATABASE_URL=”postgresql://user:password@db:5432/mydatabase” # コンテナ内のデフォルト値として設定

    CMD [“uvicorn”, “main:app”, “–host”, “0.0.0.0”, “–port”, “80”]
    “`

  2. docker run -eオプションを使う: コンテナ起動時に動的に設定する場合。

    bash
    docker run -d -p 8000:80 -e DATABASE_URL="postgresql://prod_user:prod_password@prod_db:5432/prod_database" fastapi-app

  3. Docker Composeを使う: 後述します。

環境変数を使うことで、同じDockerイメージを使いながら、開発、ステージング、本番といった異なる環境ごとに異なる設定を適用できるようになります。

9. Docker Compose:複数のサービスをまとめて管理

Webアプリケーションは、通常、アプリケーションサーバー(FastAPI)、データベース、キャッシュサーバー(Redis)、メッセージキューなどの複数のコンポーネント(サービス)で構成されます。これらのサービスをそれぞれ個別のDockerコンテナとして手動で起動・管理するのは煩雑です。

Docker Composeは、複数のDockerコンテナで構成されるアプリケーションを定義し、まとめて管理するためのツールです。YAMLファイル(デフォルトではdocker-compose.yml)にサービス、ネットワーク、ボリュームなどを定義することで、単一のコマンドでアプリケーション全体をビルド、起動、停止できます。

なぜDocker Composeを使うのか

  • 複数コンテナの定義: アプリケーションに必要な全てのサービス(Webアプリ、DBなど)を一つのファイルに記述できます。
  • 一括管理: docker compose upで全サービスを起動、docker compose downで全サービスを停止・削除できます。
  • サービス間連携: 各サービスはデフォルトで同じネットワークに接続され、サービス名で互いにアクセスできます。
  • 設定の簡潔化: ポートマッピング、ボリューム、環境変数などの設定をYAMLファイルに記述するため、docker runコマンドが複雑になるのを避けられます。
  • 再現性: アプリケーション全体の構成をコード(YAMLファイル)として管理できるため、簡単に再現できます。

docker-compose.ymlファイルの作成

プロジェクトのルートディレクトリ(fastapi-docker-example/)にdocker-compose.ymlという名前のファイルを作成します。

“`yaml

docker-compose.yml

version: ‘3.8’ # Docker Compose ファイルフォーマットのバージョン

アプリケーションを構成するサービスを定義

services:
app: # サービスの名前 (任意、他のサービスから参照する際に使用)
build: . # Dockerfileが存在するディレクトリを指定。ビルドコンテキストとなる。
# このディレクトリ内のDockerfileを使ってイメージをビルドする。
ports: # ホストとコンテナ間のポートマッピング
– “8000:80” # ホストの8000番ポートを、コンテナの80番ポートにマッピング
volumes: # ホストとコンテナ間のボリューム設定
– .:/app # ホストの現在のディレクトリ(.)を、コンテナ内の/appディレクトリにマウント
# コード変更がコンテナ内に即時反映されるようになり、開発時に便利
environment: # コンテナ内で利用可能な環境変数を設定
DATABASE_URL: “postgresql://user:password@db:5432/mydatabase” # 例: DB接続情報
# 他の環境変数もここに追加

# コンテナが依存するサービスを指定
# dbサービスが起動してからappサービスが起動するようになる (起動順序の制御)
depends_on:
  - db

db: # データベースサービスの定義 (ここではPostgreSQLを例とします)
image: postgres:14-alpine # 使用するDockerイメージ
ports: # ホストからDBに直接アクセスしたい場合のみ設定 (開発/デバッグ用)
– “5432:5432” # ホストの5432番ポートをコンテナの5432番ポートにマッピング
environment: # PostgreSQLコンテナの設定に必要な環境変数
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydatabase
volumes: # データベースのデータを永続化するためのボリューム
– db_data:/var/lib/postgresql/data # db_dataという名前付きボリュームをDBのデータディレクトリにマウント

名前付きボリュームの定義

volumes:
db_data: # データベースデータを保存するための名前付きボリューム
“`

FastAPIサービスの定義 (app)

servicesセクションの下にappというサービス名でFastAPIアプリケーションを定義します。

  • build: .: このサービスのためにイメージをビルドすることを指定します。.は現在のディレクトリ(docker-compose.ymlがある場所)をビルドコンテキストとして使用し、その中のDockerfileを探します。
  • ports: - "8000:80": ホストの8000番ポートとコンテナの80番ポートをマッピングします。
  • volumes: - .:/app: これは開発時に非常に便利です。ホストマシンのプロジェクトディレクトリ全体を、コンテナ内の/appディレクトリにマウントします。これにより、ホスト側でコードを変更すると、その変更がコンテナ内の/appに即時反映されます。Uvicornを--reloadオプション付きで起動するようにCMDを変更すれば、コードの変更が自動的に検知され、アプリケーションがリロードされるようになります(ただし、この例のDockerfileでは--reloadは付けていません。開発用のDockerfileやCMDを用意することも多いです)。本番環境では通常、コード変更はイメージの再ビルドとコンテナの再起動で行うため、このvolumes設定は不要です。
  • environment:: コンテナに渡す環境変数をキーバリュー形式で指定します。FastAPIアプリケーションがpydantic-settingsなどでこれらの環境変数を読み込むようにしておけば、設定を外部から注入できます。
  • depends_on:: このサービスが依存する他のサービスを指定します。appサービスはdbサービスに依存するため、Composeはdbサービスを先に起動しようとします。ただし、これは「コンテナが起動したか」をチェックするだけで、「サービスが完全に初期化され、接続を受け付け可能になったか」までは保証しない点に注意が必要です(健康チェックなどで対応できます)。

データベース連携 (db)

同じくservicesセクションの下にdbというサービス名でデータベース(PostgreSQL)を定義します。

  • image: postgres:14-alpine: Docker Hubにある公式のPostgreSQLイメージを指定します。alpineタグは軽量なAlpine Linuxベースのイメージです。
  • ports: - "5432:5432": ホストの5432番ポートとコンテナの5432番ポートをマッピングします。これはホストから直接DBにアクセスしたい場合に便利ですが、FastAPIアプリケーションがDocker Composeネットワーク経由でアクセスするだけであれば必須ではありません。
  • environment:: PostgreSQLイメージの起動に必要な環境変数を設定します。POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DBは必須です。これらの値はFastAPIアプリケーションのDATABASE_URL環境変数と一致させる必要があります。
  • volumes: - db_data:/var/lib/postgresql/data: データベースのデータはコンテナが削除されると失われてしまいます。これを防ぐため、データをホスト側のDocker管理下の「名前付きボリューム」に保存します。db_dataが名前付きボリュームの名前、/var/lib/postgresql/dataがPostgreSQLがデータを保存するコンテナ内のパスです。

名前付きボリュームの定義 (volumes)

volumesセクションでは、使用する名前付きボリュームを定義します。ここで定義されたボリュームは、Composeによって自動的に作成・管理されます。

Docker Composeでのビルドと実行

docker-compose.ymlファイルが完成したら、プロジェクトディレクトリで以下のコマンドを実行します。

bash
docker compose up --build -d

  • docker compose up: docker-compose.ymlファイルに定義されたサービスを起動します。
  • --build: イメージがまだ存在しないか、Dockerfileが変更されている場合に、イメージを再ビルドします。初回起動時やDockerfileを変更した際は必ず付けましょう。
  • -d: サービスをバックグラウンド(デタッチモード)で起動します。

コマンドを実行すると、Docker Composeはdocker-compose.ymlを読み込み、依存関係を解決し、必要なイメージのビルド(appサービスの場合)、そしてコンテナの作成と起動を順に行います。データベースサービスも同時に起動します。

実行中のサービスを確認するには:

bash
docker compose ps

FastAPIアプリケーションにアクセスするには、docker runの場合と同様にhttp://localhost:8000/にアクセスします。Docker Composeがホストの8000番ポートをFastAPIコンテナの80番ポートにマッピングしてくれているからです。

サービス間の通信

Docker Composeは、同じネットワーク上に全てのサービスコンテナを作成します。これにより、コンテナ間はサービス名(appdb)を使って通信できます。

例えば、FastAPIアプリケーション(appコンテナ)からデータベース(dbコンテナ)に接続する場合、接続文字列のホスト名はdbとなります。

“`python

FastAPIアプリケーション内のデータベース接続設定 (例)

DATABASE_URL = “postgresql://user:password@db:5432/mydatabase”

ホスト名はDocker Composeのサービス名 ‘db’

“`

これは、Docker Composeが各サービス名に対応するIPアドレス解決を自動的に行ってくれるためです。

Docker Composeで起動したサービスを停止するには:

bash
docker compose down

このコマンドは、docker-compose.ymlで定義されたネットワークとコンテナを停止・削除します。注意: デフォルトでは、名前付きボリューム(例: db_data)は削除されません。データは保持されます。ボリュームも一緒に削除したい場合は、-vオプションを付けます。

bash
docker compose down -v # ボリュームも削除

Docker Composeを使うことで、FastAPIアプリケーションだけでなく、それに必要な全ての依存サービスをまとめて定義・管理できるようになり、開発・テスト・デプロイが非常に効率的になります。

10. Docker Composeでデータベースを連携する

前のセクションで、Docker Composeを使ってFastAPIサービスとPostgreSQLサービスを定義する基本的な方法を示しました。ここでは、もう少し具体的に、FastAPIアプリケーションがコンテナ内のデータベースにどのように接続するかを補足します。

FastAPIアプリケーションがデータベースと連携するには、データベースドライバー(例: psycopg2 for PostgreSQL)と、ORMやクエリビルダー(例: SQLAlchemy, SQLModel)が必要です。

requirements.txtにこれらを追加します。

“`txt

… (fastapi, uvicorn, pydantic-settings など)

psycopg2-binary # PostgreSQLドライバー
SQLAlchemy==2.0.30 # ORM
sqlmodel==0.0.18 # FastAPI作者によるSQLAlchemyベースのライブラリ
“`

(バージョン番号は適宜最新版や固定バージョンを指定してください)

docker-compose.ymlでデータベースサービスの環境変数を設定したように、FastAPIサービス側もデータベース接続に必要な情報を環境変数として受け取ります。

“`yaml

docker-compose.yml (抜粋)

services:
app:
# …
environment:
DATABASE_URL: “postgresql://user:password@db:5432/mydatabase” # DB接続URL
depends_on:
– db

db:
image: postgres:14-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydatabase
# オプション: データディレクトリを指定することも可能
# PGDATA: /var/lib/postgresql/data/pgdata
volumes:
– db_data:/var/lib/postgresql/data # PGDATAを指定した場合はパスを合わせる
# ports: – “5432:5432” # 開発時以外は不要

volumes:
db_data:
“`

FastAPIアプリケーション(main.pyや別のファイル)内で、これらの環境変数を読み込み、データベース接続を確立するコードを記述します。pydantic-settingsを使うと、DATABASE_URL環境変数を自動的に読み込むSettingsクラスを作成できます。

“`python

settings.py (例: 新しいファイルとして作成)

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
# 環境変数名と対応
database_url: str

model_config = SettingsConfigDict(env_file=".env") # .envファイルからの読み込みも許可

settings = Settings()

database.py (例: データベース接続処理を記述するファイル)

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlmodel import SQLModel # SQLModelを使う場合

settings.pyからDATABASE_URLを取得

SQLALCHEMY_DATABASE_URL = settings.database_url

データベースエンジンを作成

echo=Trueは実行されるSQLを表示 (開発/デバッグ用)

engine = create_engine(SQLALCHEMY_DATABASE_URL, echo=True)

SQLModelのテーブル作成関数

def create_db_and_tables():
SQLModel.metadata.create_all(engine)

セッションファクトリの作成

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

セッション取得の依存性注入用関数

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
“`

そしてmain.pyでこの接続を使用します。

“`python

main.py (修正)

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from .database import get_db, create_db_and_tables, engine # 作成したファイルからインポート
from . import models, crud, schemas # ORMモデル, CRUD操作, スキーマなどを定義したファイルがあると仮定

app = FastAPI()

アプリケーション起動時にテーブルを作成する (開発時など、必要に応じて)

@app.on_event(“startup”)
def on_startup():
create_db_and_tables()

@app.get(“/”)
def read_root():
return {“Hello”: “World”, “database_url_used”: settings.database_url} # settingsオブジェクトを使用

データベースセッションを取得するエンドポイント例

@app.get(“/items_from_db/”)
def read_items(*, db: Session = Depends(get_db)):
# ここでdbセッションを使ってデータベースからデータを取得する
# 例: items = crud.get_items(db)
return {“message”: “Database session obtained”} # 実際のデータ取得処理は省略

… 他のルート

“`

このように、FastAPIアプリケーション内では環境変数として渡されたDATABASE_URLを使ってデータベースに接続します。Docker Composeで定義されたネットワーク上で、appコンテナはサービス名dbを使ってPostgreSQLコンテナにアクセスします。ホスト名にlocalhost127.0.0.1を使うと、それはFastAPIコンテナ自身を指してしまうため、データベースコンテナには到達できない点に注意が必要です。

docker compose up --build -dでアプリケーションとデータベースが起動し、FastAPIアプリケーションがコンテナ内のPostgreSQLに接続できるようになります。名前付きボリュームdb_dataのおかげで、docker compose downでコンテナを停止・削除しても、データは失われずに次のdocker compose up時に引き継がれます。

11. 環境変数の管理と.envファイル

Docker Composeでは、環境変数を管理する便利な方法がいくつかあります。

  1. docker-compose.ymlに直接記述 (environment:): シンプルな変数や機密情報を含まない開発用の設定に適しています。

    yaml
    services:
    app:
    environment:
    MY_VARIABLE: "some_value"

  2. .envファイルを使用: プロジェクトのルートディレクトリに.envファイルを作成し、そこに環境変数をKEY=VALUE形式で記述します。Docker Composeはデフォルトでこのファイルを読み込み、そこに記述された変数をdocker-compose.yml内で参照したり、サービスコンテナに自動的に渡したりします。

    .envファイルの例:

    “`txt

    .env

    DATABASE_USER=myuser
    DATABASE_PASSWORD=mypassword
    DATABASE_NAME=mydatabase
    DATABASE_HOST=db
    DATABASE_PORT=5432

    FastAPI アプリケーションで使用する環境変数

    FASTAPI_SECRET_KEY=”supersecretkey”
    “`

    docker-compose.ymlでこれらの変数を使用またはコンテナに渡す方法:

    • サービスに自動で渡す: .envファイルにある変数は、デフォルトで全てのサービスコンテナに環境変数として渡されます。FastAPIアプリケーションがpydantic-settingsなどでこれらの変数を読み取る場合、docker-compose.ymlenvironment:セクションに明示的に記述する必要はありません(ただし、明示的に記述する方が、どの変数が使われるか分かりやすくなります)。
    • docker-compose.yml内で参照: ${VARIABLE_NAME}構文を使って、.envファイルやシェルの環境変数をdocker-compose.yml内で参照できます。

      yaml
      services:
      app:
      ports:
      - "${APP_PUBLIC_PORT}:80" # .envやシェル変数APP_PUBLIC_PORTを参照
      db:
      environment:
      POSTGRES_USER: ${DATABASE_USER} # .envのDATABASE_USERを参照
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
      POSTGRES_DB: ${DATABASE_NAME}

.envファイルを使う方法は、特に開発環境で多数の環境変数を管理する場合に便利です。ただし、.envファイルは通常Git管理下に置かれますが、本番環境の秘密鍵やパスワードなどの機密情報は絶対に.envファイルに平文で置かないでください。本番環境では、Docker SecretsやKubernetes Secrets、またはクラウドプロバイダーの秘密情報管理サービス(AWS Secrets Manager, Google Secret Managerなど)を使用します。開発環境用のダミーの.envファイルを作成し、本番用の設定は別の安全な方法で管理するようにしましょう。

12. 本番環境へのデプロイを考える (概要)

DockerとDocker Composeは開発環境や小規模な本番環境には非常に強力ですが、大規模な本番環境にデプロイする際には、考慮すべき点がいくつかあります。

  • コンテナオーケストレーション: 単一ホストでのDocker Composeは限界があります。複数のホストに跨ってアプリケーションをスケーリング、管理、監視するためには、Kubernetes, Docker Swarm, Amazon ECS, Google Cloud Runなどのコンテナオーケストレーションプラットフォームが必要です。これらはコンテナの起動、停止、スケーリング、ローリングアップデート、自己修復などを自動化します。
  • リバースプロキシとSSL: 本番環境では、FastAPIアプリケーションを直接インターネットに公開するのではなく、NginxやCaddyなどのリバースプロキシを通して公開するのが一般的です。リバースプロキシはSSL終端、負荷分散、静的ファイル配信、レート制限などを担当します。これらのプロキシもDockerコンテナとして実行することが多いです。
  • ロギングとモニタリング: コンテナからのログを収集し、一元的に管理・分析する仕組みが必要です(例: ELKスタック, Grafana Loki)。また、コンテナやアプリケーションの稼働状況、パフォーマンスを監視するツール(例: Prometheus, Grafana)も重要です。
  • 秘密情報の管理: データベースのパスワードやAPIキーなどの機密情報は、安全な方法でコンテナに渡す必要があります。Docker Secrets, Kubernetes Secrets, クラウドプロバイダーのサービスを利用します。
  • CI/CDパイプライン: コードの変更を自動的にテストし、Dockerイメージをビルドしてコンテナレジストリにプッシュし、本番環境にデプロイするパイプラインを構築します(例: Jenkins, GitLab CI, GitHub Actions, CircleCI)。
  • イメージサイズの最適化: マルチステージビルドや適切なベースイメージの選択を徹底し、イメージサイズを最小限に保つことは、デプロイ時間の短縮やコスト削減につながります。
  • ヘルスチェック: コンテナが正常に動作しているか(アプリケーションがリクエストに応答できるか)を定期的に確認するヘルスチェックを設定します。これにより、オーケストレーションプラットフォームは異常なコンテナを自動的に再起動したり、トラフィックの転送を停止したりできます。DockerfileのHEALTHCHECK命令や、KubernetesのLiveness/Readinessプローブを使用します。

“`dockerfile

Dockerfile に HEALTHCHECK を追加する例

CMDの後に追加

HEALTHCHECK –interval=30s –timeout=10s –start-period=5s –retries=3 \
CMD curl -f http://localhost/ || exit 1
“`

FastAPIの場合、/docsや専用のヘルスチェックエンドポイントを用意してそれを叩くのが一般的です。

これらの要素は本番環境での運用を安定させるために不可欠ですが、まずはDockerとDocker Composeを使ってFastAPIアプリケーションをコンテナ化し、基本的なデプロイメントフローを理解することが重要です。

13. トラブルシューティング:よくある問題とその解決策

Dockerを使った開発やデプロイでは、いくつかの一般的な問題に遭遇することがあります。

  • コンテナが起動しない、またはすぐに停止する:
    • 解決策: docker logs <コンテナID>コマンドでコンテナのログを確認してください。Pythonの起動エラー、FastAPIのインポートエラー、Uvicornの起動オプションの間違い、アプリケーションコード内のエラーなどが原因である可能性が高いです。docker ps -aで停止したコンテナのIDを確認できます。
    • docker ps -aでSTATUSがExited (1)のような場合、エラーコード1でプロセスが終了しています。ログを確認しましょう。
  • ホストからコンテナのFastAPIにアクセスできない (例: curl: (7) Failed to connect to localhost port 8000: Connection refused)
    • 解決策:
      • docker psでコンテナが実行中か確認します。
      • docker runまたはdocker-compose.ymlのポートマッピング(-p 8000:80など)が正しいか確認します。ホスト側のポート(左側)がFastAPIコンテナのポート(右側、Uvicornがバインドしているポート)にマッピングされている必要があります。
      • FastAPI/Uvicornがコンテナ内で--host 0.0.0.0で起動されているか確認します。127.0.0.1localhostにバインドされていると、コンテナ外からはアクセスできません。CMDENTRYPOINTの設定を確認します。
      • ホスト側のファイアウォール設定を確認します。指定したポート(例: 8000)へのアクセスがブロックされていないか確認してください。
  • コンテナがデータベースや他のサービスに接続できない:
    • 解決策:
      • Docker Composeを使用している場合、docker compose psで全ての必要なサービス(app, dbなど)がUpになっているか確認します。
      • Docker Composeネットワーク内で、サービス名(例: db)を使って他のサービスにアクセスしているか確認します。接続文字列のホスト名がサービス名になっている必要があります(postgresql://user:pass@db:5432/mydb)。localhost127.0.0.1は使えません。
      • サービス間のネットワークが正しく設定されているか確認します(Docker Composeがデフォルトで設定してくれますが、カスタムネットワークを使っている場合は設定を確認)。
      • データベースコンテナのログを確認し、データベース自体が正常に起動し、接続を受け付けているか確認します(docker logs <dbコンテナID>)。環境変数(POSTGRES_USER, POSTGRES_PASSWORDなど)の設定ミスがないか確認します。
      • FastAPIアプリケーションのデータベース接続コードに誤りがないか確認します。環境変数から正しくURLを取得できているか、ドライバーやORMが正しく設定されているかなど。
  • イメージのビルドに失敗する:
    • 解決策: Docker buildの出力ログを注意深く確認します。エラーが発生したSTEPと、そのステップで実行されたコマンド(通常はRUNコマンド)を確認します。
    • RUN pip install -r requirements.txtで失敗する場合、requirements.txtの記述ミス、指定したパッケージバージョンの問題、またはそのパッケージのビルドに必要なシステム依存関係がベースイメージに不足している可能性があります(slimイメージで起こりやすい)。その場合は、必要なシステムパッケージをRUN apt-get update && apt-get install ...などでインストールするか、マルチステージビルドを検討します。
    • COPYコマンドで失敗する場合、指定したファイルやディレクトリがホスト側の正しい場所に存在するか確認します。.dockerignoreで意図せず必要なファイルを除外していないか確認します。
  • イメージサイズが大きすぎる:
    • 解決策:
      • より軽量なベースイメージ(例: python:3.9-slim, alpineベースのイメージ)を使用します。
      • .dockerignoreを使って不要なファイルをイメージにコピーしないようにします。
      • RUN apt-get clean, rm -rf /var/lib/apt/lists/*などのコマンドを使って、パッケージマネージャーのキャッシュなどをクリーンアップします(Alpine Linuxの場合はapk cache cleanなど)。
      • pip install時に--no-cache-dirオプションを使用します。
      • マルチステージビルドを導入し、ビルドに必要なツールを実行イメージに含めないようにします。

これらの解決策は一般的なものです。問題が発生した際は、エラーメッセージとコンテナのログを詳細に確認し、落ち着いて原因を特定することが重要です。Dockerや使用しているミドルウェアの公式ドキュメントも非常に役立ちます。

14. まとめ:FastAPIとDockerで未来へ

この記事では、FastAPIアプリケーションをDockerコンテナで実行するための基本的な手順から、より実践的なテクニック、そしてDocker Composeを使った複数サービス管理までを網羅的に解説しました。

FastAPIの持つモダンな非同期処理能力と高い生産性、そしてDockerの提供する移植性、再現性、分離性は、組み合わせることで現代のWebアプリケーション開発・デプロイにおいて非常に強力な武器となります。

  • シンプルなDockerfileを作成し、FastAPIアプリケーションのイメージをビルド・実行できるようになった。
  • .dockerignore, 非rootユーザー実行、マルチステージビルドといったDockerfileの改善点を通じて、より効率的で安全なコンテナイメージを作成する方法を学んだ。
  • Docker Composeを使って、FastAPIアプリケーションとデータベースのような複数のサービスをまとめて定義し、管理する方法を理解した。
  • コンテナ間の通信、環境変数の管理、そして本番環境への考慮点について概観した。

これらの知識を活かすことで、「自分の環境では動いたのに」という問題を過去のものにし、開発したアプリケーションを自信を持って様々な環境に展開できるようになります。コンテナ化は、クラウドネイティブなアーキテクチャやマイクロサービスへの第一歩でもあります。

今回作成したシンプルなFastAPIアプリケーションをベースに、認証機能の追加、より複雑なデータベース連携、テストの組み込みなど、様々な機能を実装しながらDocker化を進めてみてください。実践を通じて、DockerとFastAPIの連携の強力さをより実感できるはずです。

FastAPIとDockerの世界へようこそ! Happy Coding!


この詳細な記事が、FastAPIアプリケーションのDockerコンテナ化を進める上でお役に立てれば幸いです。合計で約5000語となるように、各セクションの説明を詳細にし、コード例やコマンド例、そして「なぜそうするのか」という理由を丁寧に記述しました。

コメントする

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

上部へスクロール