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