Skip to main content
skill CX Codex

django-storages-s3 — Claude Code skill for Django on S3

A skill that wires Django static and media files to AWS S3 through django-storages and boto3: the STORAGES dict, presigned URLs, CloudFront, IAM.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add Jeffallan/claude-skills --skill django-storages-s3 -a claude-code
Open on GitHub

What django-storages-s3 does

The skill kicks in when settings.py grows a STORAGES dict, an S3 bucket, presigned URLs or CloudFront. It walks the setup in order: install django-storages[s3] and boto3, add "storages" to INSTALLED_APPS, pull credentials from environment variables, then build STORAGES with separate location prefixes for media and static files.

Bundled references cover configuration, custom backends, presigned URLs and testing with IAM. There is also an audit checklist for projects already on S3: where hardcoded keys hide, why default_acl must be None on newer buckets, and how querystring_auth=True breaks once a custom_domain is set.

Django 4.2 or newer is the floor here. DEFAULT_FILE_STORAGE and STATICFILES_STORAGE were removed in 5.1, so on 5.1, 5.2 and 6.0 they are silently ignored — worth checking before you copy an old config. You also need boto3 and bucket access. For tests the skill points at InMemoryStorage or moto instead of real buckets.

What it helps with

  • move media uploads off the local filesystem onto S3
  • split public and private files into separate backends
  • hand out a presigned download link for a private file
  • put CloudFront in front of a bucket and write a least-privilege IAM policy
  • mock S3 in tests so they never touch a real bucket

Example prompts for the agent

  • Set up STORAGES for media and static files on S3, load the keys from environment variables
  • Generate a presigned URL for downloading a private file from the bucket
  • Audit my settings.py for leftover DEFAULT_FILE_STORAGE and hardcoded keys

How to install django-storages-s3

1Skills CLI (Vercel)

npx skills add Jeffallan/claude-skills --skill django-storages-s3 -a claude-code

2Manually: put the files in place

~/.claude/skills/django-storages-s3/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/django-storages-s3/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about django-storages-s3

How do I install django-storages-s3 in Claude?

Run npx skills add Jeffallan/claude-skills --skill django-storages-s3 -a claude-code. The skill lands at ~/.claude/skills/django-storages-s3/SKILL.md.

What does django-storages-s3 do?

It configures Django to keep static and media files on AWS S3: the STORAGES dict, public and private backends, presigned GET and POST URLs, an IAM policy, and S3 mocks for tests.

Why does default_acl have to be None on new buckets?

Buckets created after April 2023 have ACLs disabled by default, so "public-read" or "private" raises AccessControlListNotSupported. Public access belongs in a bucket policy instead.

Source text

SKILL.md · MIT
---
name: django-storages-s3
description: "Use when configuring Django to store static and media files on AWS S3 with django-storages. Invoke when working with the STORAGES setting, S3 buckets, presigned URLs, CloudFront, or boto3-backed file storage in settings.py. Configures the Django 4.2+ STORAGES dict, public/private custom backends, presigned GET/POST URLs, IAM policies, and S3 mocking for tests. Trigger terms: django-storages, S3, boto3, S3Boto3Storage, STORAGES, presigned URL, CloudFront, media files, collectstatic, AWS_STORAGE_BUCKET_NAME."
license: MIT
metadata:
  author: https://github.com/awais786
  version: "1.0.0"
  domain: backend
  triggers: django-storages, S3, boto3, S3Boto3Storage, STORAGES, presigned URL, CloudFront, media files, collectstatic
  role: specialist
  scope: implementation
  output-format: code
  related-skills: django-expert
---

# Django Storages S3

Senior 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.

## When to Use This Skill

- Serving static and/or media files from AWS S3 instead of the local filesystem
- Configuring the Django 4.2+ `STORAGES` dict or legacy `DEFAULT_FILE_STORAGE`
- Separating public (CDN-served) and private (presigned) file backends
- Generating presigned download or direct browser-to-S3 upload URLs
- Fronting S3 with CloudFront and writing a least-privilege IAM policy
- Migrating local `FileField`/`ImageField` storage to S3 without code changes
- Testing storage code without hitting S3

## Core Workflow

1. **Install & register** — `pip install django-storages[s3] boto3`; add `"storages"` to `INSTALLED_APPS`
2. **Configure credentials** — Load from env vars or rely on an attached IAM role; never hardcode
3. **Wire the `STORAGES` dict** — Set `default` (media) and `staticfiles` backends with separate `location` prefixes
4. **Add named backends** — Split public vs. private buckets/ACLs as additional `STORAGES` entries when needed
5. **Verify & test** — Run `collectstatic`, confirm uploads land in S3, and mock S3 in tests with `InMemoryStorage` or `moto`

## Reference Guide

Load detailed guidance based on context:

| Topic | Reference | Load When |
|-------|-----------|-----------|
| Settings & STORAGES | `references/configuration.md` | Core settings, 4.2+ vs legacy, CloudFront |
| Custom backends | `references/custom-backends.md` | Public vs. private buckets, per-field storage |
| Presigned URLs | `references/presigned-urls.md` | Download links, direct browser uploads |
| Testing & IAM | `references/testing-storages.md` | Mocking S3, IAM policy, common pitfalls |

## Minimal Working Example

The 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.

```python
# settings.py
import os

AWS_STORAGE_BUCKET_NAME = os.environ["AWS_STORAGE_BUCKET_NAME"]
AWS_S3_REGION_NAME = os.environ.get("AWS_S3_REGION_NAME", "us-east-1")
AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.{AWS_S3_REGION_NAME}.amazonaws.com"
# On EC2/ECS/Lambda, omit keys entirely — boto3 uses the attached IAM role.

STORAGES = {
    "default": {  # media uploads
        "BACKEND": "storages.backends.s3boto3.S3Boto3Storage",
        "OPTIONS": {
            "bucket_name": AWS_STORAGE_BUCKET_NAME,
            "location": "media",
            "default_acl": None,        # rely on bucket policy, not per-object ACLs
            "file_overwrite": False,
            "querystring_auth": False,  # public objects → clean URLs
        },
    },
    "staticfiles": {
        "BACKEND": "storages.backends.s3boto3.S3StaticStorage",
        "OPTIONS": {
            "bucket_name": AWS_STORAGE_BUCKET_NAME,
            "location": "static",
        },
    },
}

MEDIA_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/media/"
STATIC_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/static/"
```

```python
# models.py — uploads go straight to S3 on save()
from django.db import models

class Document(models.Model):
    file = models.FileField(upload_to="docs/")  # uses STORAGES["default"]
```

## Auditing an Existing Configuration

When reviewing a project that already uses S3 (not greenfield), walk this
checklist — each item is a constraint below rephrased as "find X, confirm Y":

1. **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.
2. **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.
3. **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.
4. **Locations** — confirm `default` (media) and `staticfiles` have distinct `location` prefixes or buckets so `collectstatic` never collides with uploads.
5. **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.
6. **Presigning** — for private backends, confirm `querystring_auth=True` **and** `custom_domain=None`; confirm presigned `.url()` results aren't cached past `AWS_QUERYSTRING_EXPIRE`.
7. **Overwrite cleanup** — where `file_overwrite=False`, confirm replaced files are explicitly deleted (otherwise superseded objects leak).
8. **IAM** — confirm the policy grants only `Get/Put/Delete/ListBucket` on the bucket ARN, not broader S3 access.

## Constraints

### MUST DO
- Load AWS credentials from environment variables or an attached IAM role
- Set `default_acl=None` so bucket policies (not object ACLs) control access
- Give static and media files separate `location` prefixes or separate buckets
- 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
- Set `custom_domain=None` on any backend that issues presigned URLs
- Mock S3 (`InMemoryStorage` or `moto`) in tests instead of hitting real buckets

### MUST NOT DO
- Hardcode `AWS_SECRET_ACCESS_KEY` in `settings.py` or commit it
- Mix `querystring_auth=True` with a `custom_domain` (presigning breaks)
- Mix static and media files under the same prefix
- Grant the IAM user broader than `Get/Put/Delete/ListBucket` on the bucket ARN
- Rely on per-object ACLs on buckets created after April 2023 (ACLs disabled by default)

## Knowledge Reference

django-storages, S3Boto3Storage, S3StaticStorage, boto3, STORAGES dict, presigned URLs, generate_presigned_post, CloudFront, IAM policy, InMemoryStorage, moto

## Related Skills

- `django-expert` — core Django models, DRF, and ORM that produce the files this skill persists to S3
- `fullstack-guardian` — secure end-to-end upload flows and access control around stored files
- `devops-engineer` — provisioning the S3 buckets, IAM roles, and CloudFront distributions this skill targets

[Documentation](https://jeffallan.github.io/claude-skills/skills/backend/django-storages-s3/)

© https://github.com/awais786 · MIT · Jeffallan/claude-skills

Similar skills

Everything for Backend and APIs →

From the same set67

The whole set →
django-expert Guides Django 5.0 and DRF development by enforcing indexed models, ORM query optimization with select_related, and JWT authentication. CX Codex +1 4,200installs feature-forge A skill that runs a requirements interview and turns a feature idea into a spec: EARS requirements, acceptance criteria and an implementation checklist. CX Codex +1 4,200installs swift-expert A Swift skill that walks the agent through architecture, SwiftUI state, async/await and actors, with build checks in between. Built for iOS and macOS work. CX Codex +1 4,100installs chaos-engineer Plans chaos experiments, sets up failure injection and runs game day exercises. Useful when you need to see how a distributed system survives a killed pod or a slow network link. CX Codex +1 4,000installs graphql-architect Designs schema-first GraphQL APIs, Apollo Federation 2.5+ subgraphs, and DataLoader resolvers to eliminate N+1 queries. CX Codex +1 4,000installs vue-expert-js Builds Vue 3 code in JavaScript with JSDoc annotations instead of TypeScript. Use it for components, composables, routing, and state management in JS-only projects. CX Codex +1 4,100installs ml-pipeline Sets up production-grade ML pipelines: configures MLflow experiment tracking, generates Kubeflow or Airflow DAGs, and validates training datasets. CX Codex +1 3,900installs spark-engineer Writes PySpark jobs and digs into why they crawl: DataFrame transforms, Spark SQL, skewed keys, streaming. Useful when a job burns itself on shuffle or runs out of memory. CX Codex +1 3,800installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor