Django中級 #6 Static/Media の運用と storage backends
基礎 #5 で静的ファイルを初めて扱いました。開発サーバ (python manage.py runserver) が勝手に配信してくれるモードでした。運用に移ると、その自動が消えます — Django は運用で静的ファイルを配信しないのが慣例 です。
今回はそのギャップを埋めます。2 種類のファイルと、それを扱う運用パターンまで。
- Static — 開発者が作ったファイル (CSS/JS/画像/フォント)
- Media — ユーザーがアップロードしたファイル (プロフィール写真、添付など)
Static vs Media — 2 つを分離する理由 #
| Static | Media | |
|---|---|---|
| 出処 | 開発者 (リポジトリにコミット) | ユーザーアップロード |
| 変更頻度 | デプロイ単位 | リアルタイム |
| バックアップの必要性 | 低い (リポジトリにある) | 高い (失うと終わり) |
| CDN | ほぼ常に | 時々 |
| キャッシング | 強いキャッシュ (ハッシュファイル名) | 普通/短いキャッシュ |
この違いから Django は 2 つのシステムを分離してあります。設定も別、ハンドリングも別。
Static — 開発者が作ったファイル #
3 つの設定 — STATIC_URL、STATICFILES_DIRS、STATIC_ROOT
#
名前が似ていてよく混乱する 3 つです。
STATIC_URL = "/static/"
STATICFILES_DIRS = [
BASE_DIR / "static", # プロジェクト全体の静的ファイル
]
STATIC_ROOT = BASE_DIR / "staticfiles" # collectstatic の出力先| 設定 | 意味 | 用途 |
|---|---|---|
STATIC_URL | ブラウザが見る URL prefix | /static/css/style.css |
STATICFILES_DIRS | 開発時に追加で探すディレクトリ | プロジェクト全体の静的ファイル |
STATIC_ROOT | collectstatic が集めた結果 | 運用時に nginx が配信 |
加えて、アプリ別の static/ ディレクトリ は AppDirectoriesFinder が自動で見つけてくれます。blog/static/blog/style.css のような形。
テンプレートで #
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'images/logo.png' %}" alt="logo">
<script src="{% static 'js/app.js' %}"></script>{% static %} タグが STATIC_URL + ファイルパスを組み合わせてくれます。
collectstatic — 運用デプロイの要
#
開発時は 複数の箇所に散らばった 静的ファイルを:
- アプリ別の
static/群 STATICFILES_DIRSのディレクトリ群- 外部ライブラリ (
django.contrib.adminなど) の静的ファイル
collectstatic が 1 か所 (STATIC_ROOT) に集めてくれます。
python manage.py collectstatic --noinput--noinput は「既存のファイルを上書きしますか?」のようなプロンプトを自動で yes 処理。CI で必須。
デプロイフロー:
1. コード pull / イメージビルド
2. python manage.py migrate ← DB スキーマ
3. python manage.py collectstatic --noinput ← 静的ファイルを集める
4. 新プロセス起動 (gunicorn 再起動など)STATIC_ROOT は git ignore
#
STATIC_ROOT は collectstatic がデプロイごとに新しく作るディレクトリです。git に入れないでください。
staticfiles/
media/Media — ユーザーアップロードファイル #
モデル — FileField、ImageField
#
from django.db import models
class Profile(models.Model):
user = models.OneToOneField("auth.User", on_delete=models.CASCADE)
avatar = models.ImageField(upload_to="avatars/%Y/%m/", blank=True)
class Post(models.Model):
title = models.CharField(max_length=200)
cover = models.ImageField(upload_to="covers/", blank=True)
attachment = models.FileField(upload_to="attachments/%Y/%m/", blank=True)upload_to の動作:
"avatars/"—MEDIA_ROOT/avatars/以下に保存"covers/%Y/%m/"— 年/月別フォルダ自動生成 (covers/2026/05/)- callable も可能 — 関数で動的パス
ImageField は Pillow が必要です。
pip install Pillow設定 — MEDIA_URL、MEDIA_ROOT
#
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"開発時の配信 — 一行 #
Django は運用でメディアを配信しませんが、開発時は便宜上 一行で有効化できます。
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)if settings.DEBUG ガードで囲んで 運用では動作しない ように防ぎます。
テンプレートで #
{% if user.profile.avatar %}
<img src="{{ user.profile.avatar.url }}" alt="avatar">
{% endif %}avatar.url が MEDIA_URL + 保存パスを自動で組み合わせます。
運用 — Django は静的ファイルを配信しない #
運用環境での静的/メディア配信の標準パターン 3 つ:
パターン 1 — nginx が直接 #
最も伝統的で最も速い方法。
server {
listen 80;
server_name myblog.com;
location /static/ {
alias /srv/myblog/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /srv/myblog/media/;
expires 30d;
}
location / {
proxy_pass http://127.0.0.1:8000; # gunicorn
}
}/static/ と /media/ は nginx が直接ディスクから配信。それ以外のリクエストだけ gunicorn (Django) へ。
パターン 2 — クラウドストレージ (S3 など) #
スケールが大きくなったり多重サーバになるとディスク共有が難しくなります。S3、GCS、Azure Blob のようなオブジェクトストレージに移します。
パターン 3 — WhiteNoise (小さなアプリ用) #
シンプルなアプリでは nginx なしで gunicorn 前段で静的ファイルを配信 できます。
django-storages + S3 — 本格パターン
#
pip install django-storages[s3] boto3INSTALLED_APPS = [
...
"storages",
]
# AWS 資格情報 (環境変数から読むことを推奨)
AWS_ACCESS_KEY_ID = os.environ["AWS_ACCESS_KEY_ID"]
AWS_SECRET_ACCESS_KEY = os.environ["AWS_SECRET_ACCESS_KEY"]
AWS_STORAGE_BUCKET_NAME = "my-blog-media"
AWS_S3_REGION_NAME = "ap-northeast-2"
AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com"
AWS_DEFAULT_ACL = None
AWS_QUERYSTRING_AUTH = False # 公開バケットなら
# Django 4.2+ の STORAGES 設定
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}こうしておけば Post.cover.save(...) のような呼び出しが 自動で S3 にアップロード されます。モデルコードは 1 行も変わりません。cover.url も自動で S3 URL。
Django 4.2+ の STORAGES 設定
#
Django 4.2 から DEFAULT_FILE_STORAGE / STATICFILES_STORAGE が deprecated。統合された STORAGES dict を使います。
STORAGES = {
"default": { # メディア (モデルの FileField/ImageField)
"BACKEND": "storages.backends.s3.S3Storage",
},
"staticfiles": { # 静的ファイル
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}静的ファイルも S3 に — CDN 効果 #
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {"location": "media"},
},
"staticfiles": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {"location": "static"},
},
}collectstatic が自動で S3 にアップロードします。CloudFront のような CDN を前段に置くとグローバルに速い静的配信。
セキュリティ一行 — 資格情報 #
AWS キーをコード/リポジトリに入れないでください。環境変数、シークレットマネージャ、IAM Role どこに置くにせよコード外。
AWS_ACCESS_KEY_ID = os.environ["AWS_ACCESS_KEY_ID"]
AWS_SECRET_ACCESS_KEY = os.environ["AWS_SECRET_ACCESS_KEY"]EC2/ECS/EKS なら IAM Role を付けてキー自体をコードで扱わないのが最も安全な答えです。
WhiteNoise — 小さなアプリの正解 #
別の nginx も S3 も負担な小さなアプリでは WhiteNoise が答えです。gunicorn 前段で静的ファイルを直接配信します。
pip install whitenoiseMIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # SecurityMiddleware の直後
...
]
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}CompressedManifestStaticFilesStorage とは:
- manifest — ファイル内容のハッシュをファイル名に含む (
style.abc123.css)。変更時に新しい URL になりキャッシュ無効化が自動 - compressed — gzip / brotli 圧縮版を事前に作っておく
python manage.py collectstatic --noinputWhiteNoise が STATIC_ROOT から直接配信します。nginx なしで Heroku / Railway / Fly のような PaaS に上げるとき最もよくある答え。
Static のみ、Media は不可 #
WhiteNoise は ユーザーアップロード (media) は扱いません。Media は依然 S3 のような外部ストレージが必要です。ユーザーアップロードがないアプリなら WhiteNoise だけで十分。
Storage バックエンド抽象化
#
Django はすべてのファイル保存を Storage 抽象クラスの後ろに置きます。FileField の storage= 引数でモデル別に異なるストレージも可能。
from storages.backends.s3 import S3Storage
private_storage = S3Storage(bucket_name="my-private-bucket", default_acl="private")
class Document(models.Model):
file = models.FileField(upload_to="docs/", storage=private_storage)非公開文書は別の非公開バケット、公開画像はデフォルトバケット、のような分離が可能です。
Pre-signed URL — 非公開ファイルの共有 #
S3 バケットが非公開でも 時間制限 URL を発行して一時的にアクセスさせられます。
url = private_storage.url(document.file.name)
# https://...amazonaws.com/...?X-Amz-Algorithm=...&Expires=...AWS_QUERYSTRING_AUTH = True と AWS_QUERYSTRING_EXPIRE = 3600 (1 時間) のような設定で期限を制御します。
画像処理 — 一行案内 #
アップロードされた画像を サムネイル/リサイズ する必要があるなら、よくあるライブラリ 2 つ:
| ライブラリ | 特徴 |
|---|---|
| django-imagekit | モデルフィールドに specs を定義、リクエスト時または事前に生成 |
| sorl-thumbnail | テンプレートタグで即時生成、キャッシュ |
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Profile(models.Model):
avatar = models.ImageField(upload_to="avatars/")
avatar_thumb = ImageSpecField(
source="avatar",
processors=[ResizeToFill(150, 150)],
format="JPEG",
options={"quality": 85},
)サイズ別の変換をモデルに宣言し、ライブラリが勝手に生成。画像が重ければバックグラウンドジョブ (DRF #4 Celery) に流すパターンもよくあります。
実戦チェックリスト — 運用デプロイ前 #
-
DEBUG = False -
ALLOWED_HOSTS明示 -
STATIC_ROOT設定 +.gitignore登録 -
python manage.py collectstatic --noinputがデプロイスクリプトに含まれる - nginx / WhiteNoise / S3 のうちどの方法で静的ファイルを配信するか決定
- メディアファイルのバックアップポリシー (S3 ならバージョン管理 / ライフサイクル)
- AWS 資格情報は環境変数 / IAM Role
- セキュリティヘッダ (#5 —
SECURE_SSL_REDIRECT、HSTSなど)
まとめ #
今回押さえたもの:
- Static vs Media — 開発者/ユーザー、変更頻度、バックアップ必要性が異なる 2 種類
STATIC_URL(ブラウザ prefix)、STATICFILES_DIRS(開発時の検索パス)、STATIC_ROOT(collectstatic の出力)collectstatic— デプロイ時に散らばった静的ファイルを 1 か所に集めるMEDIA_URL、MEDIA_ROOT、モデルのFileField/ImageField、upload_to- 開発時のみ
static(settings.MEDIA_URL, ...)で配信 - 運用では Django が静的ファイルを配信しない — nginx / S3 / WhiteNoise
django-storages[s3]+STORAGES(4.2+) で S3 への切り替え- WhiteNoise — 小さなアプリの答え、ミドルウェア一行 +
CompressedManifestStaticFilesStorage Storage抽象化でモデル別に異なるストレージ- Pre-signed URL で非公開ファイルの時間制限共有
- 画像処理:
django-imagekit、sorl-thumbnail
次回 (#7 テスト) では、中級の最後 — テスト です。django.test.TestCase、fixtures、factory_boy、pytest-django まで一カ所で。