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

> Learn how Yappuccino’s Django production environment configures AWS S3 for media storage. Discover how to conditionally set S3Boto3Storage and ensure all uploads go directly to S3.

- Repository: [Ja'farbek Yusupov/yappuccino](https://github.com/jafarbekyusupov/yappuccino)
- Tags: architecture
- Published: 2026-03-04

---

**In the Yappuccino Django application, AWS S3 is configured for media storage in production by conditionally reading environment variables in [`blogpost/production.py`](https://github.com/jafarbekyusupov/yappuccino/blob/main/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`](https://github.com/jafarbekyusupov/yappuccino/blob/main/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`](https://github.com/jafarbekyusupov/yappuccino/blob/main/blogpost/production.py). The application requires three mandatory variables to activate S3 storage:

```python

# 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:

```python

# 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:

```python

# 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:

```python

# 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:

```python

# 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`](https://github.com/jafarbekyusupov/yappuccino/blob/main/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`](https://github.com/jafarbekyusupov/yappuccino/blob/main/blogpost/settings.py) (lines 14-33), ensuring that production behavior remains consistent even if [`production.py`](https://github.com/jafarbekyusupov/yappuccino/blob/main/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`](https://github.com/jafarbekyusupov/yappuccino/blob/main/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`](https://github.com/jafarbekyusupov/yappuccino/blob/main/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`](https://github.com/jafarbekyusupov/yappuccino/blob/main/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.