MENU

PythonアプリのDocker化実践ガイド|Flask+PostgreSQL

当ページのリンクには広告が含まれています。

これまでにDockerの基礎(コンテナとイメージ、基本コマンド、Dockerfile、Docker Compose、ボリューム、ネットワーク)を学んできました。その知識を定着させ、実務で使えるスキルへと昇華させるために、今回は「既存のアプリケーションをDocker化する」という実践的な演習を行います。

このガイドを通じて、学んだ知識を統合し、実際の開発プロセスでDockerを活用するイメージを掴みましょう。

目次

1. 今回のお題:シンプルなPython Webアプリケーション

今回は、Pythonの軽量なWebフレームワークであるFlaskを使用し、PostgreSQLデータベースに接続するシンプルなWebアプリケーションをDocker化します。

プロジェクトのファイル構成例

Docker化するにあたり、以下のようなファイル構成を想定します。

my-web-app/
 ├── src/                   # アプリケーションのソースコード 
 │    ├── app.py            # Flaskアプリケーション本体 
 │    └── requirements.txt  # Pythonの依存ライブラリリスト
 ├── .env.example           # 環境変数テンプレート(Git管理用) 
 ├── .env                   # 環境変数ファイル(Git管理外)
 ├── .gitignore             # Git管理から除外するファイル 
 ├── .dockerignore          # Dockerビルドコンテキストから除外するファイル 
 ├── Dockerfile             # コンテナイメージのレシピ (本番用)
 ├── docker-compose.yml     # 複数コンテナの定義 (本番用)
 └── docker-compose.dev.yml # 開発用設定(オプション)

サンプルコード

src/app.py (Flaskアプリケーション)

データベースに接続し、バージョン情報を取得して表示する簡単なAPIエンドポイントを持ちます。ヘルスチェック用エンドポイントも用意します。

import os
import time
import psycopg2
from flask import Flask, jsonify

app = Flask(__name__)

# --- データベース接続関数 (リトライ機能付き) ---
def get_db_connection():
    database_url = os.environ.get('DATABASE_URL')
    if not database_url:
        raise ValueError("DATABASE_URL environment variable is not set")
    
    max_retries = 10
    retry_delay = 5
    for i in range(max_retries):
        try:
            # 接続タイムアウトを設定
            conn = psycopg2.connect(database_url, connect_timeout=10)
            print(f"Database connection successful! (attempt {i+1})")
            return conn
        except psycopg2.OperationalError as e:
            print(f"Database connection failed (attempt {i+1}): {e}")
            if i < max_retries - 1:
                print(f"Retrying in {retry_delay} seconds...")
                time.sleep(retry_delay)
            else:
                print("Max retries reached. Giving up.")
                raise # 最終的なエラーを再送出
    # この部分は理論上到達しないはずだが念のため
    raise Exception("Failed to connect to database after multiple retries.")

# --- ヘルスチェック用エンドポイント ---
@app.route('/health')
def health_check():
    # 簡単なチェック(実際にはDB接続なども確認するとより良い)
    return jsonify({'status': 'healthy'}), 200

# --- ルートエンドポイント (DBバージョン表示) ---
@app.route('/')
def hello_db():
    try:
        conn = get_db_connection()
        cur = conn.cursor()
        cur.execute("SELECT version()")
        db_version = cur.fetchone()
        cur.close()
        conn.close()
        return jsonify({
            'message': 'Database connection successful!',
            'db_version': db_version[0] if db_version else "N/A" # fetchone()がNoneを返す可能性に対処
        })
    except Exception as e:
        # エラー発生時は500エラーを返す
        return jsonify({'error': f'Database connection failed: {str(e)}'}), 500

# --- アプリケーション実行 ---
if __name__ == '__main__':
    # 環境変数 FLASK_DEBUG が 'true' ならデバッグモードで起動
    debug_mode = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true'
    # 外部からアクセスできるよう host='0.0.0.0' を指定
    app.run(debug=debug_mode, host='0.0.0.0', port=5000)

src/requirements.txt (Python依存ライブラリ)

アプリケーションの実行に必要なライブラリをリストします。gunicorn は本番環境でFlaskアプリを実行するためのWSGIサーバーです。

Flask==2.2.2
psycopg2-binary==2.9.3
gunicorn

2. ステップ・バイ・ステップ Docker化プロセス

ステップ1: Dockerfile を作成する (本番環境向け)

アプリケーションを実行するためのDockerイメージを作成します。本番環境向けには、マルチステージビルドを採用して最終的なイメージサイズを小さくし、非rootユーザーでアプリケーションを実行してセキュリティを強化するのがベストプラクティスです。

# Dockerfile: 本番向け

# -------------------------------------------------------------
# ステージ 1: ビルドステージ (依存関係のインストール)
# -------------------------------------------------------------
# Python 3.9 の軽量イメージをベースにする
FROM python:3.9-slim as builder

# 作業ディレクトリを設定
WORKDIR /app

# まず requirements.txt だけコピーする (キャッシュ効率化のため)
COPY ./src/requirements.txt .

# 依存ライブラリを /app/dependencies にインストール
# --prefix を使うことで、実行ステージでコピーしやすくする
RUN pip install --no-cache-dir --prefix=/app/dependencies -r requirements.txt

# -------------------------------------------------------------
# ステージ 2: 実行ステージ (アプリケーションの実行環境)
# -------------------------------------------------------------
# 同じく Python 3.9 の軽量イメージをベースにする
FROM python:3.9-slim

# セキュリティ向上のため、専用の非rootユーザーとグループを作成
# --system: システムアカウントとして作成
# --no-create-home: ホームディレクトリを作成しない
# --disabled-password: パスワードログインを無効化
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 --gid 1001 --no-create-home --disabled-password appuser

# 作業ディレクトリを設定
WORKDIR /app

# ビルドステージからインストール済みの依存関係をコピー
# /usr/local 以下にコピーすることで、Pythonが自動で認識する
COPY --from=builder /app/dependencies /usr/local

# アプリケーションコードを作業ディレクトリにコピーし、所有者を非rootユーザーに変更
# COPY命令はデフォルトでroot所有になるため --chown で指定
COPY --chown=appuser:appgroup ./src/ .

# 非rootユーザーに切り替え
USER appuser

# アプリケーションがリッスンするポートをドキュメント化 (実際に公開するのは docker run や docker-compose)
EXPOSE 5000

# 環境変数 WORKERS を設定可能にし、Gunicornを使ってアプリケーションを起動
# ${WORKERS:-4} は、環境変数 WORKERS が未設定の場合にデフォルト値 4 を使う記法
CMD ["gunicorn", "-w", "${WORKERS:-4}", "-b", "0.0.0.0:5000", "app:app"]
  • マルチステージビルド: builder ステージで pip install を行い、必要なファイルだけを実行ステージにコピーすることで、最終イメージにビルドツールなどが含まれるのを防ぎ、軽量化します。
  • 非rootユーザー: adduser/addgroup で専用ユーザーを作成し、USER 命令で切り替えることで、コンテナ内での権限昇格リスクを低減します。
  • キャッシュ効率化: requirements.txt を先にコピーして pip install を実行し、その後でソースコード (./src/) をコピーすることで、ソースコードの変更だけで pip install が再実行されるのを防ぎます。

ステップ2: docker-compose.yml ファイルを作成する

Webアプリケーション (web) とデータベース (db) の2つのサービスを定義し、連携させます。

.dockerignore ファイル

Dockerfileと同じ階層に作成し、ビルドコンテキストに含めたくないファイルやディレクトリを指定します。

# .dockerignore
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.git
.env
*.log

docker-compose.yml (本番用)

本番環境での実行を想定した設定です。

# docker-compose.yml (本番用)
services:
  web:
    build: . # カレントディレクトリの Dockerfile を使ってビルド
    ports:
      - "5000:5000" # ホストの5000番をコンテナの5000番にマッピング
    env_file:
      - .env # .env ファイルから環境変数を読み込む
    depends_on:
      # db サービスの healthcheck が healthy になるまで web の起動を待つ
      db:
        condition: service_healthy
    healthcheck:
      # アプリケーションの /health エンドポイントにアクセスして正常性を確認
      test: ["CMD-SHELL", "python -m urllib.request http://localhost:5000/health || exit 1"]
      interval: 30s # チェック間隔
      timeout: 10s  # タイムアウト時間
      retries: 3    # リトライ回数
      start_period: 30s # 起動後、最初のチェック開始までの猶予時間

  db:
    image: postgres:13 # 公式のPostgreSQL 13イメージを使用
    volumes:
      # 名前付きボリュームを使ってデータを永続化
      - postgres_data:/var/lib/postgresql/data
    env_file:
      - .env # .env ファイルから環境変数を読み込む (DB接続情報など)
    healthcheck:
      # pg_isready コマンドで PostgreSQL が接続を受け付けるか確認
      test: ["CMD-SHELL", "pg_isready -U $POSTGRES_USER || exit 1"] # $ でエスケープが必要
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

volumes:
  # データを永続化するための名前付きボリュームを定義
  postgres_data:

docker-compose.dev.yml (開発用 – オプション)

開発時の効率を高めるための設定を記述します。本番用設定を上書き・追加する形で使用します。

# docker-compose.dev.yml (開発用)
services:
  web:
    # Dockerfile を使わず、ローカルのPython環境を使う場合は build をコメントアウト/削除
    # build: .
    volumes:
      # ホストの src ディレクトリをコンテナの /app にマウント (コード変更を即時反映)
      - ./src:/app
    environment:
      # Flask のデバッグモードを有効化 (コード変更時の自動リロードなど)
      - FLASK_DEBUG=true
      # Python出力をバッファリングしない (ログがリアルタイムで表示される)
      - PYTHONUNBUFFERED=1
    # 開発時は Flask の開発サーバーを使うため CMD を上書き (DockerfileのCMDは無視される)
    command: ["flask", "run", "--host=0.0.0.0", "--port=5000"]
    # healthcheck は開発時には不要な場合が多いので無効化も検討
    # healthcheck:
    #   disable: true

  # db サービスは docker-compose.yml の設定をそのまま利用

ステップ3: 環境設定ファイルを作成する

.env.example (テンプレート)

# .env.example
DATABASE_URL=postgresql://<DB_USER>:<DB_PASSWORD>@db:5432/<DB_NAME>
POSTGRES_USER=<DB_USER>
POSTGRES_PASSWORD=<DB_PASSWORD>
POSTGRES_DB=<DB_NAME>

# For Development (docker-compose.dev.yml)
FLASK_DEBUG=true

# For Production (Dockerfile CMD) - Optional
# WORKERS=4

.env (実際の値 – Git管理外!)

このファイルは .gitignore に追加して、リポジトリにコミットしないように絶対に注意してください。

# .env
DATABASE_URL=postgresql://postgres:secret_password@db:5432/myappdb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=secret_password
POSTGRES_DB=myappdb

FLASK_DEBUG=true

# WORKERS=4 # 本番環境でGunicornのワーカー数を変更する場合

.gitignore

.env ファイルがGitリポジトリに含まれないように設定します。

# .gitignore
.env
__pycache__/
*.pyc
# 他にもPythonの仮想環境ディレクトリ(venv/, env/)なども追加すると良い

ステップ4: アプリケーションの起動と確認

開発環境での起動:

-f オプションで docker-compose.ymldocker-compose.dev.yml の両方を指定して起動します。

# 初回起動時やDockerfileを変更した場合は --build を付ける
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build

# 2回目以降 (Dockerfileに変更がない場合)
# docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

本番環境での起動:

docker-compose.yml のみを指定して起動します。

# 初回起動時やDockerfileを変更した場合は --build を付ける
docker compose -f docker-compose.yml up -d --build

# 2回目以降
# docker compose -f docker-compose.yml up -d

動作確認:

Webブラウザで http://localhost:5000 にアクセスし、データベースのバージョン情報が表示されれば成功です。

停止とクリーンアップ:

# 開発環境の停止・削除
docker compose -f docker-compose.yml -f docker-compose.dev.yml down

# 本番環境の停止・削除
# docker compose -f docker-compose.yml down

# ボリューム (DBデータ) も含めて完全に削除する場合
# docker compose -f docker-compose.yml down -v

まとめ

この実践演習を通じて、既存のアプリケーションをDocker化する具体的なプロセスを体験しました。

  • Dockerfile: アプリケーションの実行環境をコード化し、再現性を確保します。
  • Docker Compose: Webサーバーとデータベースなど、複数のコンテナを連携させてアプリケーション全体を定義・管理します。
  • 環境変数 (.env): 機密情報や環境依存の設定をコードから分離し、安全に管理します。

これで、あなたはDockerの基礎知識を実践的なスキルへと昇華させることができました。この経験を活かして、ぜひご自身のプロジェクトのDocker化に挑戦してみてください。

【推奨】業務システム化に有効なアイテム

生成AIを学ぶ

システム化のパートナー

VPSサーバの選定

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

CAPTCHA


目次