Store Django Media Files on Azure Blob Storage
Store Django uploads in Azure Blob Storage with django-storages: create a storage account and private container, authenticate with managed identity instead of account keys, serve files with SAS URLs, and add Azure CDN or Front Door for public assets.
Problem statement
Django apps on Azure App Service or Container Apps should not keep user uploads on the instance: App Service's local disk is not shared between instances, and container filesystems reset on each revision. Azure Blob Storage holds the files instead. With django-storages' Azure backend, FileField uploads go to a blob container. The details to get right are authentication (prefer managed identity over account keys), private access with shared access signature (SAS) URLs, and network rules.
pip install "django-storages[azure]".- Create a storage account (StorageV2) and a private container named
media. - Enable a managed identity on the App Service or Container App and grant it
Storage Blob Data Contributoron the account. - Configure
STORAGES["default"]withstorages.backends.azure_storage.AzureStorage,account_name,azure_container, and a token credential. - Serve private files with user delegation SAS URLs (
expiration_secs), or put public assets behind Azure Front Door. - Disable public blob access on the account and restrict networks if possible.
Step-by-step solution
1. Create the account and container
az storage account create -n myappmedia -g myapp-rg -l westeurope --sku Standard_LRS \
--kind StorageV2 --allow-blob-public-access false --min-tls-version TLS1_2
az storage container create --account-name myappmedia -n media --auth-mode login
Choose ZRS or GRS redundancy for production data that matters.
2. Use a managed identity
az webapp identity assign -g myapp-rg -n myapp
PRINCIPAL=$(az webapp identity show -g myapp-rg -n myapp --query principalId -o tsv)
az role assignment create --assignee $PRINCIPAL --role "Storage Blob Data Contributor" \
--scope $(az storage account show -n myappmedia -g myapp-rg --query id -o tsv)
The app then authenticates with Azure AD tokens obtained automatically; no account key is stored anywhere.
3. Configure Django
from azure.identity import DefaultAzureCredential
STORAGES = {
"default": {
"BACKEND": "storages.backends.azure_storage.AzureStorage",
"OPTIONS": {
"account_name": "myappmedia",
"azure_container": "media",
"token_credential": DefaultAzureCredential(),
"expiration_secs": 300, # SAS lifetime for generated URLs
"overwrite_files": False,
},
},
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}
DefaultAzureCredential uses the managed identity in Azure and your az login session locally. With a token credential, the backend generates user delegation SAS URLs, signed with a key obtained via Azure AD rather than the account key.
4. Serving files
- Private files (invoices, user documents): templates call
{{ doc.file.url }}, which returns a SAS URL valid forexpiration_secs. Check permissions in the view before rendering the link. - Public assets (avatars, product images): a separate container with anonymous read can be served through Azure Front Door or CDN for caching. Keep the default container private.
5. Network and lifecycle
Restrict the storage account to selected networks or a private endpoint when the app runs in a VNet. Add lifecycle management rules to move old files to the Cool tier or delete temporary uploads. Enable soft delete for blobs so accidental deletions can be recovered.
Verification check: upload a file through the app and confirm it appears in the media container; open the generated URL (it contains se= expiry and sig= parameters) and confirm it stops working after expiry; check the account shows no access keys in use in your app settings.
Explanation
Azure Blob Storage authorises requests either with account keys (full access), SAS tokens (scoped, time-limited signatures), or Azure AD tokens (identity-based, governed by RBAC roles). Managed identities give Azure-hosted apps an Azure AD identity without stored secrets, and user delegation SAS URLs let that identity hand out time-limited read links without exposing a key. django-storages wraps this in Django's storage API, so application code uses FileField normally.
Edge cases / notes
- Local development with
DefaultAzureCredentialneedsaz loginand a role assignment for your user, or use Azurite (the local emulator). - Large uploads through Django tie up workers; issue an upload SAS and let the browser upload directly, with CORS configured on the account.
- Content types. Set
content_typecorrectly so browsers render images and PDFs inline. - Static files are usually better served by WhiteNoise or a CDN than by a Blob container.
Next steps
- Platform guides: Deploy Django on Azure App Service and Deploy Django on Azure Container Apps.
- Harden uploads with the Django File Upload Security Checklist.
FAQ
How does Django connect to Azure Blob Storage without keys?
With a managed identity and DefaultAzureCredential, granted a Storage Blob Data role.
What is a SAS URL?
A link with a signed, time-limited permission to read a blob, so private files can be served directly.
Should the media container be public?
No for private uploads. Use a separate public container behind a CDN for public assets only.
Can I use the account connection string?
It works but grants full account access. Prefer managed identity in Azure.