Static & Media Files on S3
In production your Django app server should run code, not stream files. Cloud object storage like Amazon S3 — wired up with django-storages and boto3 — gives you durable, scalable storage for both your collected static assets and your users' uploaded media.
Learn Static & Media Files on S3 in our free Django course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Django course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll configure the modern STORAGES setting with S3Boto3Storage backends, supply credentials safely from the environment, push files up with collectstatic , choose between public and signed URLs, and put a CDN in front for fast global delivery.
What You'll Learn in This Lesson
1 Why Object Storage Instead of the App Server
When DEBUG = False , Django stops serving static files, and serving user uploads through gunicorn ties up workers and doesn't survive a redeploy on ephemeral disks. Amazon S3 is durable, effectively unlimited object storage that any number of servers can read from — perfect for both your collected static assets and your users' media.
The two pieces you need are django-storages (which provides storage backends) and boto3 (the AWS SDK it uses to talk to S3).
2 Configuring the STORAGES Setting
Django 4.2+ centralizes storage configuration in a single STORAGES dict. The default entry handles media (user uploads) and the staticfiles entry handles your collected static assets. Each points at S3Boto3Storage , and the bucket, region, and credentials come from environment variables — never hard-coded.
3 collectstatic, Public vs Signed URLs, and a CDN
With the staticfiles backend pointed at S3, collectstatic uploads every static file straight into the bucket. For media, public assets can be world-readable, while private files are served with time-limited signed URLs . In front of the bucket you usually place a CDN like CloudFront for fast, cheap delivery.
Because the backend speaks the S3 API, the very same setup works with S3-compatible stores like MinIO , Cloudflare R2 , and DigitalOcean Spaces — you just point AWS_S3_ENDPOINT_URL at their endpoint.
🎯 Your Turn: Point the Backend at S3
Fill in the blank with the backend class string that django-storages provides for Amazon S3.
Common Errors (And How to Fix Them)
❌ AccessDenied when uploading or reading objects
Your credentials or IAM policy don't allow the action on that bucket, or the keys are wrong.
✅ Fix: set AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY from the environment (or an IAM role) and grant the user s3:PutObject and s3:GetObject on the bucket.
❌ Static files load locally but 404 in production
You configured the S3 staticfiles backend but never pushed the files to the bucket.
✅ Fix: run python manage.py collectstatic --noinput so the files are uploaded to s3://your-bucket/static/ .
❌ Secrets leaked because they were hard-coded
AWS keys or the bucket name were written directly in settings.py and committed to git.
✅ Fix: read every credential from os.environ , keep them out of version control, and rotate any key that was exposed.
📋 Quick Reference
Setting / Tool
What It Does
django-storages + boto3
Provide the S3 backend and the AWS SDK it uses
STORAGES
Configure default (media) and staticfiles backends
S3Boto3Storage
The backend that reads/writes objects on S3
AWS_STORAGE_BUCKET_NAME
The bucket files are stored in (from an env var)
collectstatic
Upload collected static files into the bucket
🏆 Mini Challenge: Build the Asset URL
Combine the bucket name, the location prefix, and the file name to build the public URL Django would hand the browser — exactly what STATIC_URL + the storage backend produce.
❓ Frequently Asked Questions
Lesson 28 complete — and you've finished the Django scaling track!
You can now serve static and user-uploaded media from cloud object storage with django-storages and boto3 , configure the STORAGES setting with S3Boto3Storage backends, keep credentials in environment variables, push files with collectstatic , choose between public and signed URLs, and put a CloudFront CDN in front — even on S3-compatible stores like MinIO, R2, and Spaces.
🏆 You've completed the Django scaling track! From full-text search and Docker deployment to serving assets from S3 behind a CDN, you now have the production toolkit to ship Django apps that scale. Revisit any lesson whenever you need a refresher, and keep building.
Practice quiz
Which two packages let Django store files on Amazon S3?
- django-storages and boto3
- pillow and requests
- whitenoise and gunicorn
- psycopg2 and redis
Answer: django-storages and boto3. django-storages provides the storage backends and boto3 is the AWS SDK it uses to talk to S3.
Which Django setting (4.2+) configures storage backends for default and staticfiles?
- FILE_BACKENDS
- DJANGO_STORAGE
- STORAGES
- MEDIA_BACKENDS
Answer: STORAGES. The STORAGES setting, added in Django 4.2, holds a dict with default and staticfiles backend entries.
Which backend class does django-storages provide for S3?
- BucketStorage
- AmazonStorage
- S3Boto3Storage
- CloudStorage
Answer: S3Boto3Storage. S3Boto3Storage from storages.backends.s3boto3 is the backend that reads and writes objects on S3.
How should AWS credentials be supplied to your settings?
- Pasted into the template
- Read from environment variables
- Hard-coded directly in settings.py
- Committed to git for convenience
Answer: Read from environment variables. Credentials should come from environment variables (or an IAM role), never hard-coded or committed to version control.
Which command uploads your collected static files to the S3 bucket?
- python manage.py uploadstatic
- python manage.py runserver
- python manage.py migrate
- python manage.py collectstatic
Answer: python manage.py collectstatic. With the staticfiles backend pointed at S3, collectstatic copies all static files straight into the bucket.
Why shouldn't you serve user-uploaded media from gunicorn in production?
- The app server is for application logic, not for streaming static bytes at scale
- gunicorn cannot read files
- Media files are always private
- It is impossible to configure
Answer: The app server is for application logic, not for streaming static bytes at scale. Object storage and a CDN serve files far more efficiently and let the WSGI app server focus on requests, not byte streaming.
What is a common reason to put CloudFront in front of an S3 bucket?
- To compile Python
- To cache files at edge locations for faster, cheaper delivery
- To run database migrations
- To replace gunicorn
Answer: To cache files at edge locations for faster, cheaper delivery. CloudFront is a CDN that caches your objects near users, reducing latency and offloading requests from S3.
How are private S3 objects served securely to authorized users?
- By emailing the file
- By disabling HTTPS
- By making the whole bucket public
- With time-limited signed (presigned) URLs
Answer: With time-limited signed (presigned) URLs. For private files, S3Boto3Storage can generate signed URLs that grant temporary, expiring access without exposing the object publicly.
Which setting names the bucket that django-storages writes to?
- S3_TARGET
- MEDIA_BUCKET_PATH
- AWS_STORAGE_BUCKET_NAME
- STATIC_BUCKET
Answer: AWS_STORAGE_BUCKET_NAME. AWS_STORAGE_BUCKET_NAME tells S3Boto3Storage which bucket to read from and write to.
django-storages with the S3 backend also works with which S3-compatible stores?
- Only local disk
- MinIO, Cloudflare R2, and DigitalOcean Spaces
- Only Google Sheets
- Only Amazon S3
Answer: MinIO, Cloudflare R2, and DigitalOcean Spaces. Because they expose the S3 API, MinIO, Cloudflare R2, and DigitalOcean Spaces work with the same backend by changing the endpoint URL.
Continue this course
- Previous: Full-Text Search & Docker Deployment