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.

Media on Azure BlobDjango uploadFileField saveAzure backenddjango-storagesPrivate containermanaged identitySAS URLtime-limited readManaged identity removes account keys; SAS URLs serve private files directly from Blob Storage.
  1. pip install "django-storages[azure]".
  2. Create a storage account (StorageV2) and a private container named media.
  3. Enable a managed identity on the App Service or Container App and grant it Storage Blob Data Contributor on the account.
  4. Configure STORAGES["default"] with storages.backends.azure_storage.AzureStorage, account_name, azure_container, and a token credential.
  5. Serve private files with user delegation SAS URLs (expiration_secs), or put public assets behind Azure Front Door.
  6. 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.

authentication optionsSecret to manageRecommendationManaged identity✓none✓use in AzureConnection string / key✕full account keyavoid; rotate if usedAccount SAS token!long-lived tokennarrow scope, expiryAn account key grants full control of every container in the accountdjango-deployment.com
Grant the identity a data role on the account or container, not Contributor on the resource group.

4. Serving files

  • Private files (invoices, user documents): templates call {{ doc.file.url }}, which returns a SAS URL valid for expiration_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.

Azure Blob media setupPublic blob access disabledon the accountManaged identity with data roleno account keysSAS URLs for private filesshort expirySoft delete and lifecycle rulesrecovery and costUnique upload pathsoverwrite_files offIdentity-based access plus short-lived links keeps private files privatedjango-deployment.com
Soft delete is cheap insurance against a buggy delete in your code.

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 DefaultAzureCredential needs az login and 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_type correctly 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

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.

Keep going