Django REST FrameworkのDocker化:肥大化したイメージから軽量なプロダクション環境へ

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

DjangoアプリケーションのDocker化:「動けばいい」で終わらせない

よくある光景:DjangoのDockerイメージをビルドし終えて、そのサイズが1GBを超えていることに愕然とする。サーバーへのデプロイは遅く、不要なビルドツールが居座っているせいでRAMの使用率は常にレッドゾーン…

私自身、ECサイトの30以上のコンテナを管理していた際に、同じような状況に陥ったことがあります。その時、最適化は単なる趣味ではなく、クラウドコストを抑えるための死活問題でした。マルチステージビルド(Multi-stage build)を採用することで、イメージサイズを900MBから180MBにまで減らすことができました。その結果、CI/CDの時間も5分から45秒未満に短縮されました。

この記事では、Django REST Framework (DRF) プロジェクトをプロフェッショナルな形で本番環境にデプロイするための、標準的な構成を共有します。

クイックスタート:5分で起動

急いでいる方は、プロジェクトのルートディレクトリに以下の構成で Dockerfiledocker-compose.yml を作成して、すぐに成果を確認してみてください。

. 
├── core/ (Djangoプロジェクト)
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env

次のコマンドひとつでスタック全体を起動できます:

docker-compose up --build

アプリが正常に動作することを確認したら、細部を最適化して本物のプロダクション仕様に仕上げる方法を詳しく見ていきましょう。

1. マルチステージビルドの極意:イメージの軽量化

なぜPythonのイメージは重くなりがちなのでしょうか?それは、psycopg2Pillow といったライブラリがコンパイルのために gccmusl-dev を必要とするからです。これらのツールは非常に重いですが、アプリが実行される段階では全く不要です。さらなるイメージの軽量化を目指すなら、ビルドプロセスを分離することが不可欠です。

# ステージ1: Builder - ライブラリのビルド環境
FROM python:3.11-slim as builder

WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && apt-get install -y \
    build-essential libpq-dev --no-install-recommends

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt

# ステージ2: Final - 実行に必要なものだけを保持
FROM python:3.11-slim

WORKDIR /app
# セキュリティ向上のため非rootユーザーを作成
RUN addgroup --system app && adduser --system --group app

RUN apt-get update && apt-get install -y libpq-dev --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/wheels /wheels
RUN pip install --no-cache /wheels/*

COPY . .
RUN chown -R app:app /app
USER app

CMD ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:8000"]

この方法により、ビルドツール一式(gcc、ヘッダーファイルなど)はステージ1に置き去りにされます。最終的なイメージは非常に軽量になり、root権限で実行されないためセキュリティも向上します。

2. Docker Compose:Web、Celery、Redisの連携

実際のDRFシステムでは、重いタスクを処理するためにバックグラウンドワーカーが不可欠です。ポイントは、WebアプリとCeleryワーカーで同じ Dockerfile を共有することです。これにより、サービス間で環境の100%の一致が保証されます。

version: '3.8'

services:
  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file: .env

  redis:
    image: redis:7-alpine

  web:
    build: .
    command: gunicorn core.wsgi:application --bind 0.0.0.0:8000
    volumes: [ ".:/app" ]
    ports: [ "8000:8000" ]
    env_file: .env
    depends_on: [ db, redis ]

  worker:
    build: .
    command: celery -A core worker --loglevel=info
    env_file: .env
    depends_on: [ db, redis ]

volumes:
  postgres_data:

補足:depends_on はDBコンテナが先に起動することのみを保証し、DBが接続を受け入れ可能になるまで待機するわけではありません。本番環境では、wait-for-it スクリプトなどを併用することをお勧めします。

3. Entrypoint의 処理とマイグレーションの自動化

サーバー上で手動でマイグレーションを実行するのは避けましょう。entrypoint.sh ファイルを介して、起動時にDockerが自動で行うように設定します。

#!/bin/sh

# マイグレーション実行前にDBの準備ができているか確認
python manage.py migrate --noinput
python manage.py collectstatic --noinput

exec "$@"

chmod +x entrypoint.sh コマンドを忘れないでください。この手順を怠ると、コンテナ起動時に「Permission denied」エラーが発生します。

4. トラブルを避けるための実戦経験

.dockerignoreで不要なファイルを排除

イメージにコピーされるファイルが増えるほど、サイズが大きくなりセキュリティリスクも高まります。.dockerignore を作成して、.git__pycache__、不要な .env ファイルなどを除外しましょう。

リソース制限(Resource Limits)

Djangoのメモリリークのバグは、VPS全体をダウンさせる可能性があります。composeファイルで各サービスに対して常にRAM制限を設定しましょう。例えば、中規模のDjangoインスタンスであれば memory: 512M あたりが妥当な数値です。

スマートなログ管理

デフォルトでは、DockerはログをJSON形式で保存し、ディスクがいっぱいになるまで肥大化し続けます。スマートなログ管理のために max-size: "10m" を設定してログを自動ローテーションさせ、深夜にサーバーが突然停止するような事態を防ぎましょう。

まとめ

Djangoを正しくDocker化することは、単に動くDockerfileを書くことだけではありません。それはイメージサイズ、ビルド速度、そしてセキュリティのバランスを取る芸術です。

上記のマルチステージ構成とDocker Compose의 構成により、混乱することなく数十のコンテナにアプリをスケールさせるための強固な基盤が整いました。プロジェクトが大規模になる場合は、静的ファイルやSSLの処理のためにNginxの追加を検討してください。デプロイの成功を祈っています!

Share: