Skip to content

Static & Media files in Production

Deploying files in Django involves two different concepts:

  1. Static Files: These are your CSS, JavaScript and theme images. They are part of your application code. You deploy them by running the collectstatic command, which gathers them into the folder defined by your STATIC_ROOT setting.
  2. Media Files: These are files uploaded by your users during runtime (like profile pictures or documents). Django automatically saves these to the location defined by your default storage backend, which locally defaults to your MEDIA_ROOT folder.

Every production setup is slightly different. The sections below outline the most common deployment patterns for handling both types of files.

Serving the site and your files from the same server

You might choose to serve both static and media files from the same server running your Django application. To do this you upload your code to the server and run collectstatic. You then configure your web server like Apache or Nginx to serve the files inside STATIC_ROOT under your STATIC_URL path and the files inside MEDIA_ROOT under your MEDIA_URL path. Automating this process is highly recommended for projects with multiple web servers.

Serving files from a dedicated server

Larger Django projects often use a completely separate web server just for file serving. This separate server runs a faster and more stripped-down web server program. Because this server does not run Django you must change your deployment strategy.

For static files, you run collectstatic on your local machine or a dedicated build server. You then transfer the resulting STATIC_ROOT directory directly to the dedicated static server. A tool like rsync works perfectly for this because it only transfers files that have actually changed.

For media files, the rsync approach does not work because users upload files continuously. To serve media from a dedicated server, your main Django application servers and your dedicated file server must share access to the same storage volume (like an NFS drive). This ensures files saved by Django are instantly available to the web server.

Serving files from a cloud service or CDN

Serving files from a cloud storage provider or a Content Delivery Network is highly popular. This approach completely removes the hassle of managing shared file systems for media uploads and results in much faster loading times for your users.

If your provider offers an API you can use a custom file storage backend to automate the transfer. You tell Django to use these custom backends by updating the STORAGES dictionary setting. The staticfiles key controls where your static assets go, while the default key controls where user-uploaded media files are saved.

python
STORAGES = {
    "default": {
        "BACKEND": "mysite.storage.S3Storage",  # Media files go to the cloud
    },
    "staticfiles": {
        "BACKEND": "django.core.files.storage.FileSystemStorage", # Static stays local
    },
}

Using django-storages with AWS S3

The easiest way to connect Django to a cloud provider is by using the django-storages library. This is a widely used collection of custom storage backends that handles all the complex API interactions for you.

WARNING

Keeping static files on AWS can sometimes result in 403 errors on the admin page if permissions are not perfectly configured. For many projects, only media files (user uploads) are stored in S3, while static files are served locally by the web server.

To get started, install the required packages:

shell
python -m pip install django-storages boto3 Pillow cryptography

(Optional) CloudFront Setup

It is highly recommended to use a Content Delivery Network like AWS CloudFront to serve your S3 files. CloudFront caches your files at edge locations around the world, drastically reducing load times. Follow these steps to set it up:

  • Create a Public Key Pair using the following commands:
shell
openssl genrsa -out private_key.pem 2048
openssl rsa -pubout -in private_key.pem -out public_key.pem
  • Navigate to CloudFront Public Keys Console and create a public key using the key from above.
    • Make a note of the Key ID (This becomes the AWS_CLOUDFRONT_KEY_ID below).
    • The Private Key from previous step becomes the AWS_CLOUDFRONT_KEY below.
  • Navigate to CloudFront Key Groups Console and create a key group using the public key from above.
  • Navigate to CloudFront Distributions Console and edit the current distribution (or create a new distribution).
    • For edits, just update the Key group under Behavior tab.
    • For new creation, use following:
      • Origin domain: <s3 bucket>
      • Origin access: Origin access control settings (recommended)
        • Create a new OAC, if not already existing for the bucket.
      • Viewer protocol policy: Redirect HTTP to HTTPS
      • Allowed HTTP methods: GET, HEAD, OPTIONS (No cache for OPTIONS)
      • Restrict viewer access: Yes
      • Response headers policy: CORS-with-preflight-and-SecurityHeadersPolicy
      • Web Application Firewall (WAF): Do not enable security protections
      • Description: Pirate Dev Media
  • Create the distribution and save the generated bucket policy. You will need it during the S3 bucket setup.
    • Make a note of the CloudFront domain name (Ex: d123456.cloudfront.net). This becomes your AWS_S3_CUSTOM_DOMAIN below.

Configure Django

Next, you must add storages to your INSTALLED_APPS list.

You then configure Django to use S3 by updating the STORAGES dictionary in your settings file. You also need to provide your AWS access keys, the region and the name of your bucket.

Notice that in this configuration we assign the S3 backend to default (for media) while leaving staticfiles on the local backend:

python
# settings.py

INSTALLED_APPS = [
    # ...
    "storages",
]

# AWS S3 Configuration
AWS_ACCESS_KEY_ID = "your-access-key"
AWS_SECRET_ACCESS_KEY = "your-secret-key"
AWS_STORAGE_BUCKET_NAME = "your-bucket-name"
AWS_S3_REGION_NAME = "ap-south-2"
AWS_S3_SIGNATURE_VERSION = 's3v4'
# To expire S3 URL in 4 hours
AWS_QUERYSTRING_EXPIRE = 14400
AWS_S3_ADDRESSING_STYLE = "virtual"
AWS_S3_ENDPOINT_URL = f"https://s3.{AWS_S3_REGION_NAME}.amazonaws.com"
AWS_S3_FILE_OVERWRITE = False
AWS_DEFAULT_ACL = None
AWS_S3_VERIFY = True

# Optional: AWS CloudFront CDN Configuration
AWS_S3_CUSTOM_DOMAIN = "your-cloudfront-domain"
AWS_CLOUDFRONT_KEY_ID = "your-cloudfront-key-id"
AWS_CLOUDFRONT_KEY = "your-cloudfront-key-string"

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
    },
    "staticfiles": {
        "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
    },
}

Configure AWS S3 Bucket

If you are not using CloudFront, you need to set a CORS rule in your AWS S3 bucket to allow frontend downloads. You can do this under the Bucket Permissions tab:

json
[
    {
        "AllowedHeaders": [
            "Authorization"
        ],
        "AllowedMethods": [
            "GET",
            "HEAD"
        ],
        "AllowedOrigins": [
            "https://yourdomain.com",
            "https://*.yourdomain.com",
            "https://www.*.yourdomain.com"
        ],
        "ExposeHeaders": [
            "Access-Control-Allow-Origin"
        ],
        "MaxAgeSeconds": 3000
    }
]

If you are using CloudFront, you must update the S3 bucket policy using the JSON generated during the CloudFront setup (replace the <account_id> and <distribution_id> placeholders):

json
{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-bucket-name/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::<account_id>:distribution/<distribution_id>"
                }
            }
        }
    ]
}

Once this configuration is in place, Django will automatically save any new user-uploaded media directly to your S3 bucket. Because static files are configured to stay local, you still need to run collectstatic to gather your code assets and restart your web server.

shell
python manage.py collectstatic && sudo systemctl restart apache2