How AWS S3 is Configured for Media Storage in Production in the Yappuccino Django Project

In the Yappuccino Django application, AWS S3 is configured for media storage in production by conditionally reading environment variables in blogpost/production.py, setting DEFAULT_FILE_STORAGE to S3Boto3Storage when credentials exist, and forcibly injecting the storage instance into django.core.files.storage.default_storage to ensure all uploads—including CKEditor 5 assets—write directly to S3 with public-readable permissions.

The Yappuccino project implements an environment-driven media storage strategy that automatically provisions Amazon S3 for user uploads in production while maintaining local filesystem compatibility for development. This configuration, defined primarily in blogpost/production.py, ensures that media files are served from scalable cloud storage only when valid AWS credentials are detected, preventing runtime failures in environments without S3 access.

Environment Variable Requirements for S3 Access

The configuration begins by reading AWS credentials from the environment at lines 29-34 of blogpost/production.py. The application requires three mandatory variables to activate S3 storage:


# blogpost/production.py (lines 29-34)

AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME')  # Optional

If any of the three required credentials are absent, the system immediately falls back to local storage, ensuring the application remains functional without AWS infrastructure.

Conditional Storage Backend Configuration

Activating S3Boto3Storage

When all credentials are present, lines 50-66 configure Django to use the boto3-powered S3 backend:


# blogpost/production.py (lines 50-66)

DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

# Public-readable configuration

AWS_DEFAULT_ACL = None
AWS_QUERYSTRING_AUTH = False
AWS_S3_FILE_OVERWRITE = True

# Custom domain construction

custom_domain = f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com"
MEDIA_URL = f"https://{custom_domain}/"

This setup disables query-string authentication and clears the default ACL, making uploaded files publicly accessible via their direct URL without signed parameters.

Fallback to FileSystemStorage

If credentials are missing, the configuration at lines 80-89 reverts to Django's default local storage:


# blogpost/production.py (lines 80-89)

DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

CKEditor 5 Integration

To ensure rich-text editor uploads follow the same storage path, line 61 assigns the default storage class to CKEditor's file handler:


# blogpost/production.py (line 61)

CKEDITOR_5_FILE_STORAGE = DEFAULT_FILE_STORAGE

This guarantees that images uploaded through CKEditor 5 interfaces are written to the same S3 bucket as other media files.

Forced Default Storage Injection

A critical implementation detail occurs at lines 68-75, where the module forcibly replaces Django's global storage instance:


# blogpost/production.py (lines 68-75)

from storages.backends.s3boto3 import S3Boto3Storage
from django.core.files import storage as django_storage

# Force all file operations to use S3

django_storage.default_storage = S3Boto3Storage()

This pattern ensures that third-party applications—including CKEditor and other Django packages—that rely on django.core.files.storage.default_storage automatically write to S3 without requiring individual configuration.

Static Files vs. Media Files Separation

The configuration deliberately excludes static files from S3 hosting. According to lines 94-95 in blogpost/production.py, static assets continue to be served via WhiteNoise middleware, maintaining deployment simplicity while leveraging S3's scalability exclusively for user-generated content.

Dual Settings Coverage

The same S3 configuration logic is also embedded in the non-debug block of blogpost/settings.py (lines 14-33), ensuring that production behavior remains consistent even if production.py is not explicitly imported.

Summary

  • Credential-gated activation: AWS S3 is only enabled when AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_STORAGE_BUCKET_NAME are all present; otherwise, the system safely falls back to local FileSystemStorage as implemented in lines 80-89.
  • Public media URLs: Uploaded files receive publicly readable URLs without query-string authentication by setting AWS_DEFAULT_ACL = None and AWS_QUERYSTRING_AUTH = False at lines 54-56.
  • Global storage injection: The project overwrites django.core.files.storage.default_storage with an S3Boto3Storage instance at lines 68-75 to force all file operations—including CKEditor 5 uploads—to use S3 automatically.
  • Static file separation: WhiteNoise continues to handle static assets according to lines 94-95, while S3 exclusively manages user uploads and media files.

Frequently Asked Questions

What happens if AWS credentials are missing in production?

If any of the three required environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_STORAGE_BUCKET_NAME) are missing, the configuration in blogpost/production.py (lines 80-89) automatically sets DEFAULT_FILE_STORAGE to django.core.files.storage.FileSystemStorage and configures MEDIA_URL to serve files locally from the media/ directory. This prevents runtime crashes and enables development without AWS infrastructure.

How does Yappuccino handle CKEditor 5 file uploads with S3?

The project assigns CKEDITOR_5_FILE_STORAGE = DEFAULT_FILE_STORAGE at line 61 of blogpost/production.py, ensuring CKEditor 5 uses the same S3Boto3Storage backend as standard Django file uploads. Combined with the forced storage injection at lines 68-75, this guarantees that all images and files uploaded through the editor interface are written directly to the configured S3 bucket.

Are static files served from S3 in this configuration?

No. According to lines 94-95 in blogpost/production.py, static assets remain served by WhiteNoise middleware. The S3 configuration applies exclusively to media files (user uploads), keeping deployment pipelines simple while still benefiting from cloud scalability for dynamic content.

Why does the project overwrite the default storage instance?

By instantiating S3Boto3Storage() and assigning it to django.core.files.storage.default_storage at lines 68-75, the project ensures that any code—including third-party Django applications—that references the global default storage object automatically writes to S3 without requiring explicit storage class configuration. This pattern eliminates configuration drift between different file handling components.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →