はじめに
この記事では、DockerとDjangoを使ってブログサイトをゼロから作ります。
「Pythonのインストールは?MySQLは?」と思ったかもしれませんが、必要ありません。
Dockerさえあれば、Macに何もインストールしなくても開発環境が作れます。
この章で用意するもの:
- Docker Desktop(無料)
- VS Code(無料)
このシリーズについて:このブログのコードと記事は、人間と AI(Claude)がペアで設計・実装・検証して作ったものだよ。AIに「とりあえずコード書いて」じゃなく、設計の選択肢を比べて選ぶ → 実装 → ハマる → 一緒に潰す を繰り返した実走記録なんだ。だから「AIに丸投げで動かない」じゃなく、「人とAIで対話しながら作ると、こういう完成度になる」サンプルとして読んでね
Dockerとは?なぜ使うのか
Dockerは「仮想の箱」を作るツールです。
たとえばPythonのプログラムを動かすには、普通はMacにPythonをインストールする必要があります。しかしDockerを使うと「Pythonが入った箱」を用意するだけでOKです。Macには何も入れません。
メリット:
- MacにPython・MySQL・Nginxをインストールしなくていい
- 他の人のMacでも同じ環境が再現できる
- 本番サーバーでも同じ環境で動かせる
今回はこの3つの「箱」を用意します:
| コンテナ名 | 役割 |
|---|---|
| nginx | ブラウザからのリクエストを受け取る |
| web(Django) | ページを生成する |
| db(MySQL) | データを保存する |
完成形のフォルダ構成
作業前に完成形を確認しておきましょう。
~/myblog/
├ myblog/ ← プロジェクト設定(settings.py・urls.py等)
├ docker/ ← Nginx設定ファイル
│ └ nginx.dev.conf
├ staticfiles/ ← 静的ファイル置き場(CSS・JS・画像)
├ media/ ← アップロード画像置き場
├ manage.py ← Djangoの操作コマンド
├ .env ← 環境変数(GitHubにあげない)
├ .dockerignore ← Dockerに含めないファイルの設定
├ Dockerfile ← Dockerの設定
├ docker-compose.yml
└ requirements.txt
Step1. フォルダを作る
ターミナルを開いて以下を実行してください。
mkdir -p ~/myblog
mkdir -p ~/myblog/docker
mkdir -p ~/myblog/staticfiles
mkdir -p ~/myblog/media
cd ~/myblog
mkdir -p は「フォルダを作る」コマンドです。-p オプションをつけると、途中のフォルダも一緒に作ってくれます。
Step2. .dockerignoreを作る
touch ~/myblog/.dockerignore
中身:
.env
.git
__pycache__
*.pyc
.dockerignore は「Dockerのイメージに含めないファイル」を指定するファイルです。
特に .env はパスワードが書いてあるので、絶対にDockerイメージに含めてはいけません。
Step3. Dockerfileを作る
touch ~/myblog/Dockerfile
中身:
# ベースイメージにPython 3.12を使用
FROM python:3.12
# コンテナ内の作業ディレクトリを/app に設定
WORKDIR /app
RUN groupadd --gid 1000 app && \
useradd --uid 1000 --gid app --shell /bin/bash --create-home app
# requirements.txtをコンテナにコピー
COPY requirements.txt .
# パッケージをインストール(キャッシュなしで軽量化)
RUN pip install --no-cache-dir -r requirements.txt
# プロジェクトのコードを全てコンテナにコピー
# --chown でコピー時に所有権を app ユーザーに変更
COPY --chown=app:app . .
# 書き込みが必要なディレクトリを app 所有で用意
RUN mkdir -p /app/logs /app/staticfiles /app/media && \
chown -R app:app /app
# 以降のコマンドは非rootの app ユーザーで実行
# gunicorn は 0.0.0.0:8000 で listen するが、ポート >1024 なので非rootでOK
USER app
DockerfileはDockerの「設計図」です。「Python 3.12の環境を作って、必要なパッケージをインストールして、コードをコピーする」という手順を書いています。
Step4. requirements.txtを作る
touch ~/myblog/requirements.txt
中身:
# Django本体
django==6.0.5
# 本番用Webサーバー
gunicorn==26.0.0
# MySQLクライアント
mysqlclient==2.2.8
# 環境変数管理
django-environ==0.13.0
# 静的ファイル配信
whitenoise==6.12.0
# 画像処理
Pillow==12.2.0
# Markdown → HTML 変換(第6章で使用)
markdown==3.10.2
# コードハイライト(第6章で使用)
pygments==2.20.0
# 画像の自動リサイズ・サムネイル生成(第6章で使用)
django-imagekit==6.1.0
requirements.txt はPythonの「買い物リスト」です。pip install でインストールするパッケージを列挙します。
なぜバージョンを == で固定するのか
「django だけ書けば最新が入って良くない?」と思うかもしれませんが、これには 2つの落とし穴 があります。
| 問題 | 何が起きるか |
|---|---|
| 半年後に読者が動かせない | Django 7.0 が出たら django 指定だと新版が入る → 記事のコードと食い違って動かない |
| あなたのローカルと本番がズレる | 来月 docker compose build し直した時に別バージョンが入ってバグる |
実務でも 本番用 requirements は == で完全固定 が定石です。今回も読者全員が同じ環境を再現できるように、執筆時点で動作確認した版で固定しておきます。
バージョン更新はどうするの?って思うよね。実は GitHub の Dependabot ってボットが自動で更新PRを作ってくれるんだ。今は固定しておいて、後で安全に上げる方が安心だよ!
ちなみにこの章ではまだ markdown pygments django-imagekit は使いません。第6章で記事本文のリッチ化をするときに使います。ただ後から追加すると docker compose build をやり直す手間が増えるので、最初からまとめて入れてしまうのがオススメです。
Step5. .envを作る
touch ~/myblog/.env
中身:
# Django設定
SECRET_KEY=django-insecure-your-secret-key-here
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
# データベース設定
MYSQL_DATABASE=myblog
MYSQL_USER=blog_user
MYSQL_PASSWORD=blogpassword
MYSQL_ROOT_PASSWORD=rootpassword
DATABASE_URL=mysql://blog_user:blogpassword@db:3306/myblog
なぜ.envを使うのか?
パスワードをコードに直接書くと、GitHubにあげたときに全世界に公開されてしまいます。.env に書いておけば .dockerignore と .gitignore で除外できます。
MYSQL_PASSWORD などのパスワードは好きな文字列に変えてOKです。
Step6. docker-compose.ymlを作る
touch ~/myblog/docker-compose.yml
中身:
services:
# Nginx(Webサーバー)
nginx:
image: nginx:latest
volumes:
- ./docker/nginx.dev.conf:/etc/nginx/nginx.conf:ro
- ./staticfiles:/app/staticfiles
- ./media:/app/media
ports:
- "80:80"
depends_on:
web:
condition: service_started # webが起動したらNginxも起動する
# Django(アプリケーションサーバー)
web:
build: .
command: gunicorn myblog.wsgi:application --bind 0.0.0.0:8000 --reload
volumes:
- .:/app
- ./staticfiles:/app/staticfiles
- ./media:/app/media
env_file:
- .env
depends_on:
db:
condition: service_healthy # dbがhealthyになるまで待つ
# MySQL(データベース)
db:
image: mysql:8.0
env_file:
- .env
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 5
timeout: 5s
volumes:
mysql_data:
docker-compose.yml は3つのコンテナをまとめて管理するファイルです。
web の起動コマンドの --reload
web コンテナの起動コマンドに --reload を付けています。
command: gunicorn myblog.wsgi:application --bind 0.0.0.0:8000 --reload
--reload は .py ファイルを保存すると gunicorn が自動で再起動してくれるオプションです。これがないと、views.py などを編集しても docker compose restart web するまで反映されず、「直したのに変わらない!」とハマります。開発中はこれがあると快適です。
--reload は開発専用だよ。ファイルを監視し続けるぶん動作が重くて不安定だから、本番では絶対に使わないの。本番は第10章で別の docker-compose.prod.yml(--reload なし・--workers 付き)を作るよ。「開発用と本番用でcomposeを分ける」ようにするよ。
💡
.pyの変更は--reloadで即反映されますが、CSSや画像(static)は別です。変更後にcollectstatic(第3章で解説)が必要になります。
起動順序の制御
depends_on で起動順序を制御しています。
db が起動(healthy)
↓
web が起動(started)
↓
nginx が起動
この順番を守らないとNginxがDjangoに接続できずエラーになります。
condition の2種類
| 種類 | 意味 | 使い分け |
|---|---|---|
service_started |
コンテナが起動した瞬間にOK | webやnginxはこれで十分 |
service_healthy |
healthcheck が通った状態を待つ | DBは「起動済み ≠ 接続可能」なので必須 |
MySQLは「コンテナ起動」と「クエリ受付可能」にタイムラグがあるため service_healthy で待ちます。一方 Django・Nginx は起動直後から動けるので service_started で十分です。
Step7. nginx.dev.confを作る
touch ~/myblog/docker/nginx.dev.conf
中身:
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
# 静的ファイル(CSS・JS・画像)の配信
location /static/ {
alias /app/staticfiles/;
}
# アップロードされた画像の配信
location /media/ {
alias /app/media/;
}
# それ以外のリクエストはDjangoに転送
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
Nginxはブラウザからのリクエストを受け取り、CSSや画像は自分で配信し、それ以外はDjangoに転送します。
Step8. Djangoプロジェクトを作る
cd ~/myblog
docker compose run --rm web django-admin startproject myblog .
このコマンドの意味:
docker compose run --rm web→ webコンテナの中でコマンドを実行(終わったら削除)django-admin startproject myblog .→myblogという名前のプロジェクトを現在のフォルダに作る
末尾の . が重要です。これがないと余分なフォルダができてしまいます。
実行後のフォルダ構成:
~/myblog/
├ myblog/ ← プロジェクト設定フォルダ(自動生成)
├ manage.py ← Djangoの操作コマンド(自動生成)
└ ...
Step9. settings.pyを設定する
~/myblog/myblog/settings.py を開いて修正します。
django-environの設定
ファイルの先頭部分を以下に書き換えます:
from pathlib import Path
import environ
# BASE_DIRを先に定義する
BASE_DIR = Path(__file__).resolve().parent.parent
env = environ.Env()
environ.Env.read_env(BASE_DIR / '.env')
SECRET_KEY・DEBUG・ALLOWED_HOSTSを.envから読み込む
SECRET_KEY = env('SECRET_KEY')
DEBUG = env.bool('DEBUG')
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
日本語・タイムゾーンの設定
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
DATABASESをMySQLに変更
DATABASES = {
'default': env.db()
}
STATIC_ROOTの追加
STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
Step10. Dockerイメージをビルドする
cd ~/myblog
docker compose build
Dockerfileの内容をもとにイメージを作ります。初回は数分かかります。
確認コマンド:
docker compose run --rm web pip list | grep -E "django-environ|mysqlclient"
Step11. マイグレーションを実行する
cd ~/myblog
docker compose run --rm web python manage.py migrate
マイグレーションはDjangoがデータベースにテーブルを作る作業です。Djangoが内部で使うテーブル(ユーザー管理・セッション等)が作成されます。
Step12. 起動確認
cd ~/myblog
docker compose up
以下のようなログが流れたらDjangoが起動しています:
web-1 | [INFO] Starting gunicorn 26.0.0
web-1 | [INFO] Listening at: http://0.0.0.0:8000
ブラウザで http://localhost を開いてDjangoのデフォルト画面(ロケット🚀のウェルカムページ)が表示されれば成功です!
止めるときは別タブで:
cd ~/myblog
docker compose down
ハマりポイント
「ブラウザで localhost が ERR_CONNECTION_REFUSED になる」
docker compose ps -a でコンテナの状態を確認:
docker compose ps -a
| 症状 | 原因 | 対処 |
|---|---|---|
nginx が Created のまま |
webの起動待ちで止まってる | docker compose logs web でエラー確認 |
nginx が Exited (1) |
nginx.dev.conf の文法エラー | docker compose logs nginx でエラー確認 |
web が Exited |
settings.py の編集ミス | docker compose logs web でPythonエラー確認 |
| 80番ポートが既に使われてる | 他のサーバーが動いてる | lsof -i :80 で犯人探し |
「docker compose build でパッケージが見つからないエラー」
ERROR: Could not find a version that satisfies the requirement xxx==1.2.3
→ requirements.txt のバージョンにタイポがあるか、そのバージョンがまだ存在しない可能性。pip index versions <パッケージ名> で実在バージョンを確認しましょう。
「db が unhealthy のまま web が起動しない」
MySQLは初回起動時にデータ初期化で時間がかかります。1〜2分待ってから再度 docker compose up を試してください。それでもダメな場合:
docker compose down -v # ← -v でボリュームも消す(DBデータも消える)
docker compose up
次章ではblogアプリを作って、自分のページを表示させます。
