DjangoアプリケーションのDocker化:「動けばいい」で終わらせない
よくある光景:DjangoのDockerイメージをビルドし終えて、そのサイズが1GBを超えていることに愕然とする。サーバーへのデプロイは遅く、不要なビルドツールが居座っているせいでRAMの使用率は常にレッドゾーン…
私自身、ECサイトの30以上のコンテナを管理していた際に、同じような状況に陥ったことがあります。その時、最適化は単なる趣味ではなく、クラウドコストを抑えるための死活問題でした。マルチステージビルド(Multi-stage build)を採用することで、イメージサイズを900MBから180MBにまで減らすことができました。その結果、CI/CDの時間も5分から45秒未満に短縮されました。
この記事では、Django REST Framework (DRF) プロジェクトをプロフェッショナルな形で本番環境にデプロイするための、標準的な構成を共有します。
クイックスタート:5分で起動
急いでいる方は、プロジェクトのルートディレクトリに以下の構成で Dockerfile と docker-compose.yml を作成して、すぐに成果を確認してみてください。
.
├── core/ (Djangoプロジェクト)
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env
次のコマンドひとつでスタック全体を起動できます:
docker-compose up --build
アプリが正常に動作することを確認したら、細部を最適化して本物のプロダクション仕様に仕上げる方法を詳しく見ていきましょう。
1. マルチステージビルドの極意:イメージの軽量化
なぜPythonのイメージは重くなりがちなのでしょうか?それは、psycopg2 や Pillow といったライブラリがコンパイルのために gcc や musl-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の追加を検討してください。デプロイの成功を祈っています!

