Static & Media files in Production
Deploying files in Django involves two different concepts:
- Static Files: These are your CSS, JavaScript and theme images. They are part of your application code. You deploy them by running the
collectstaticcommand, which gathers them into the folder defined by yourSTATIC_ROOTsetting. - 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
defaultstorage backend, which locally defaults to yourMEDIA_ROOTfolder.
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.
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:
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:
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_IDbelow). - The Private Key from previous step becomes the
AWS_CLOUDFRONT_KEYbelow.
- Make a note of the Key ID (This becomes the
- 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
Behaviortab. - 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
- Origin domain:
- For edits, just update the Key group under
- 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 yourAWS_S3_CUSTOM_DOMAINbelow.
- Make a note of the CloudFront domain name (Ex:
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:
# 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:
[
{
"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):
{
"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.
python manage.py collectstatic && sudo systemctl restart apache2