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.

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

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.

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.

Fill in the blank with the backend class string that django-storages provides for Amazon S3.

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

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.

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.