{"id":"django-storages-s3","name":"django-storages-s3","summary":"DjangoのStorage Storage を使ってAWS S3上で静的およびメディアファイルを保存するためにDjangoを設定する際に使います。","body":"# Django Storages S3\n\nSenior Django specialist for production-grade file storage on AWS S3 via `django-storages` and `boto3` — public and private media, static files, presigned URLs, and CloudFront.\n\n## When to Use This Skill\n\n- Serving static and/or media files from AWS S3 instead of the local filesystem\n- Configuring the Django 4.2+ `STORAGES` dict or legacy `DEFAULT_FILE_STORAGE`\n- Separating public (CDN-served) and private (presigned) file backends\n- Generating presigned download or direct browser-to-S3 upload URLs\n- Fronting S3 with CloudFront and writing a least-privilege IAM policy\n- Migrating local `FileField`/`ImageField` storage to S3 without code changes\n- Testing storage code without hitting S3\n\n## Core Workflow\n\n1. **Install & register** — `pip install django-storages[s3] boto3`; add `\"storages\"` to `INSTALLED_APPS`\n2. **Configure credentials** — Load from env vars or rely on an attached IAM role; never hardcode\n3. **Wire the `STORAGES` dict** — Set `default` (media) and `staticfiles` backends with separate `location` prefixes\n4. **Add named backends** — Split public vs. private buckets/ACLs as additional `STORAGES` entries when needed\n5. **Verify & test** — Run `collectstatic`, confirm uploads land in S3, and mock S3 in tests with `InMemoryStorage` or `moto`\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Settings & STORAGES | `references/configuration.md` | Core settings, 4.2+ vs legacy, CloudFront |\n| Custom backends | `references/custom-backends.md` | Public vs. private buckets, per-field storage |\n| Presigned URLs | `references/presigned-urls.md` | Download links, direct browser uploads |\n| Testing & IAM | `references/testing-storages.md` | Mocking S3, IAM policy, common pitfalls |\n\n## Minimal Working Example\n\nThe snippet below demonstrates the core MUST DO constraints: env-loaded credentials, `STORAGES` dict, separate media/static locations, and `default_acl=None` on the media backend.\n\n```python\n# settings.py\nimport os\n\nAWS_STORAGE_BUCKET_NAME = os.environ[\"AWS_STORAGE_BUCKET_NAME\"]\nAWS_S3_REGION_NAME = os.environ.get(\"AWS_S3_REGION_NAME\", \"us-east-1\")\nAWS_S3_CUSTOM_DOMAIN = f\"{AWS_STORAGE_BUCKET_NAME}.s3.{AWS_S3_REGION_NAME}.amazonaws.com\"\n# On EC2/ECS/Lambda, omit keys entirely — boto3 uses the attached IAM role.\n\nSTORAGES = {\n    \"default\": {  # media uploads\n        \"BACKEND\": \"storages.backends.s3boto3.S3Boto3Storage\",\n        \"OPTIONS\": {\n            \"bucket_name\": AWS_STORAGE_BUCKET_NAME,\n            \"location\": \"media\",\n            \"default_acl\": None,        # rely on bucket policy, not per-object ACLs\n            \"file_overwrite\": False,\n            \"querystring_auth\": False,  # public objects → clean URLs\n        },\n    },\n    \"staticfiles\": {\n        \"BACKEND\": \"storages.backends.s3boto3.S3StaticStorage\",\n        \"OPTIONS\": {\n            \"bucket_name\": AWS_STORAGE_BUCKET_NAME,\n            \"location\": \"static\",\n        },\n    },\n}\n\nMEDIA_URL = f\"https://{AWS_S3_CUSTOM_DOMAIN}/media/\"\nSTATIC_URL = f\"https://{AWS_S3_CUSTOM_DOMAIN}/static/\"\n```\n\n```python\n# models.py — uploads go straight to S3 on save()\nfrom django.db import models\n\nclass Document(models.Model):\n    file = models.FileField(upload_to=\"docs/\")  # uses STORAGES[\"default\"]\n```\n\n## Auditing an Existing Configuration\n\nWhen reviewing a project that already uses S3 (not greenfield), walk this\nchecklist — each item is a constraint below rephrased as \"find X, confirm Y\":\n\n1. **Credentials** — `grep -rn \"AWS_SECRET_ACCESS_KEY\\|aws_secret\" settings/` → confirm values come from `os.environ`/`django-environ` or an IAM role, never literals committed to the repo.\n2. **ACLs** — `grep -rn \"default_acl\\|AWS_DEFAULT_ACL\" .` → on buckets created after April 2023, every value must be `None`. Any `\"public-read\"`/`\"private\"` will raise `AccessControlListNotSupported`; public access belongs in a bucket policy.\n3. **Storage backend** — confirm Django 4.2+ uses the `STORAGES` dict, not `DEFAULT_FILE_STORAGE`/`STATICFILES_STORAGE` (removed in Django 5.1, so silently ignored on 5.1/5.2/6.0); confirm the static class is `S3StaticStorage`, not a fabricated name.\n4. **Locations** — confirm `default` (media) and `staticfiles` have distinct `location` prefixes or buckets so `collectstatic` never collides with uploads.\n5. **Region** — confirm `region_name` (or the global `AWS_S3_REGION_NAME`) matches the bucket's real region and that `AWS_S3_CUSTOM_DOMAIN` includes the region segment for non-`us-east-1` buckets.\n6. **Presigning** — for private backends, confirm `querystring_auth=True` **and** `custom_domain=None`; confirm presigned `.url()` results aren't cached past `AWS_QUERYSTRING_EXPIRE`.\n7. **Overwrite cleanup** — where `file_overwrite=False`, confirm replaced files are explicitly deleted (otherwise superseded objects leak).\n8. **IAM** — confirm the policy grants only `Get/Put/Delete/ListBucket` on the bucket ARN, not broader S3 access.\n\n## Constraints\n\n### MUST DO\n- Load AWS credentials from environment variables or an attached IAM role\n- Set `default_acl=None` so bucket policies (not object ACLs) control access\n- Give static and media files separate `location` prefixes or separate buckets\n- Use the `STORAGES` dict on Django 4.2+ (same config through 5.2 LTS and 6.0); `DEFAULT_FILE_STORAGE`/`STATICFILES_STORAGE` were removed in 5.1, so reserve them for < 4.2 only\n- Set `custom_domain=None` on any backend that issues presigned URLs\n- Mock S3 (`InMemoryStorage` or `moto`) in tests instead of hitting real buckets\n\n### MUST NOT DO\n- Hardcode `AWS_SECRET_ACCESS_KEY` in `settings.py` or commit it\n- Mix `querystring_auth=True` with a `custom_domain` (presigning breaks)\n- Mix static and media files under the same prefix\n- Grant the IAM user broader than `Get/Put/Delete/ListBucket` on the bucket ARN\n- Rely on per-object ACLs on buckets created after April 2023 (ACLs disabled by default)\n\n## Knowledge Reference\n\ndjango-storages, S3Boto3Storage, S3StaticStorage, boto3, STORAGES dict, presigned URLs, generate_presigned_post, CloudFront, IAM policy, InMemoryStorage, moto\n\n## Related Skills\n\n- `django-expert` — core Django models, DRF, and ORM that produce the files this skill persists to S3\n- `fullstack-guardian` — secure end-to-end upload flows and access control around stored files\n- `devops-engineer` — provisioning the S3 buckets, IAM roles, and CloudFront distributions this skill targets\n\n[Documentation](https://jeffallan.github.io/claude-skills/skills/backend/django-storages-s3/)","author":"@Jeffallan","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/Jeffallan/claude-skills/tree/main/skills/django-storages-s3","license":"MIT","category":"writing","lang":"en","tokens":1669,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"references/configuration.md","size":6477,"sha256":"313838c81cdff849f3cc0daf2ed31d13fc2a223083d515e70f798006c5ed340b"},{"path":"references/custom-backends.md","size":5260,"sha256":"d3c31373d551407ba2c1f81b105ef80285f1c8aa3413e756a11119c06d871f7b"},{"path":"references/presigned-urls.md","size":3859,"sha256":"eda9c70c3ee495c0c3b0be89600636a96a8c15df02134c2d200b39de5421474f"},{"path":"references/testing-storages.md","size":4043,"sha256":"fd1367bf1b8dee102712a57d10b6889b5060917b30cb6314f1da86a12c10b64f"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["d123.cloudfront.net","jeffallan.github.io"]}}