Complete guide for deploying Django applications to Azure App Service (Linux) with static files, WhiteNoise, Oryx builds, and GitHub Actions CI/CD. Solves common GLIBC compatibility and static file serving issues.
Author's Note: After spending hours debugging GLIBC incompatibilities and static file issues, I created this guide so you don't have to. This approach works with Django 4.x/5.x on Azure App Service.
π― Quick Start Prompt for AI Assistants
Use this prompt with Claude, ChatGPT, or other AI assistants to get help deploying Django to Azure:
I need to deploy a Django application to Azure App Service (Linux). Here's my setup:
- Django Version: [e.g., 5.2]
- Python Version: [e.g., 3.12]
- Database: PostgreSQL on Azure / Azure Database for PostgreSQL
- Cache/Message Broker: Redis on Azure (if applicable)
- Frontend Build Tool: [e.g., Vite, Webpack, or None]
- Static Files: [e.g., "Vite builds to static/ directory" or "Standard Django static files"]
- Azure App Service (Linux) - Basic/Standard/Premium tier
- Azure Database for PostgreSQL Flexible Server
- Azure Cache for Redis (optional)
- Application Insights (optional)
- Django settings in:
[project_name]/settings.py - Requirements file:
requirements.txtorpyproject.toml - Static files: STATIC_ROOT and STATICFILES_DIRS configured
- Frontend assets (if any): Built to
[directory]
- Set up GitHub Actions CI/CD workflow to deploy to Azure App Service
- Configure Azure App Service for Django (startup command, build settings)
- Set up WhiteNoise for serving static files in production
- Configure Oryx to run collectstatic after building dependencies
- Ensure database migrations run automatically on deployment
- Build and deploy frontend assets (if applicable)
- Use Azure's Oryx build system (no Docker)
- Fast deployments (avoid rebuilding at every startup)
- Static files must be served efficiently
- Production-ready configuration
- GLIBC binary compatibility (don't pre-build Python packages in CI)
- Static file serving (Django doesn't serve static files in production by default)
- Frontend build integration with Django's collectstatic
- Oryx post-build configuration
Use the Azure Oryx remote build approach:
- GitHub Actions builds frontend assets only
- Azure Oryx builds Python dependencies on the Azure server (correct GLIBC)
- WhiteNoise serves static files
- Migrations run automatically via startup command
Please help me:
- Create/update GitHub Actions workflow (
.github/workflows/azure-deploy.yml) - Configure WhiteNoise middleware in Django settings
- Create
.oryx-config.ymlfor post-build steps - Set up Azure App Service startup command
- Update requirements.txt with all necessary dependencies
Working directory: [path to your Django project]
π¦ Required Configuration Files
- .oryx-config.yml
Place this in your project root:
version: 1
post-build: | echo "Running Django collectstatic..." python manage.py collectstatic --noinput echo "Oryx build completed successfully!"
- .github/workflows/azure-deploy.yml
GitHub Actions workflow for CI/CD:
name: Deploy to Azure App Service
on: push: branches: [ main ] workflow_dispatch:
env: AZURE_WEBAPP_NAME: your-app-name AZURE_RESOURCE_GROUP: your-resource-group
jobs: deploy: runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Build frontend if you have one (Vite/Webpack/etc)
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Build frontend assets
run: |
npm ci
npm run build
# Create deployment package (source + built frontend)
- name: Create deployment package
run: |
zip -r deploy.zip . \
-x "*.git*" "*node_modules*" "*.venv*" "*__pycache__*" "*.pyc" \
"*logs*" "*.terraform*"
# Deploy to Azure
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v3
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: deploy.zip
Note: Get AZURE_WEBAPP_PUBLISH_PROFILE from Azure Portal β App Service β Deployment Center β Manage publish profile
- Django Settings Updates
Add WhiteNoise to your settings.py:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', # Add this line 'django.contrib.sessions.middleware.SessionMiddleware', # ... rest of your middleware ]
STORAGES = { "default": { "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": { "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", }, }
STATIC_ROOT = BASE_DIR / "static_root" STATIC_URL = "/static/"
STATICFILES_DIRS = [ BASE_DIR / "static", # Your source static files ]
For Django < 4.2, use: STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
- requirements.txt
Ensure these are included:
django>=4.2 gunicorn>=21.0.0 whitenoise>=6.8.0
psycopg2-binary>=2.9.0 # For PostgreSQL
django-environ>=0.11.0 # For environment variables
If using Django Channels for WebSockets: daphne>=4.1.0 channels>=4.0.0 channels-redis>=4.0.0
βοΈ Azure App Service Configuration
Startup Command
Set in Azure Portal β Configuration β General Settings β Startup Command:
python manage.py migrate --noinput && gunicorn your_project.wsgi:application --bind 0.0.0.0:8000 --workers 4 --threads 2 --timeout 120
Replace your_project with your Django project name (the folder containing wsgi.py).
Application Settings
Add these environment variables in Azure Portal β Configuration β Application Settings:
| Setting | Value | Notes |
|---|---|---|
| SECRET_KEY | Your Django secret key | Never commit this! |
| DEBUG | False | Always False in production |
| ALLOWED_HOSTS | your-app.azurewebsites.net | Comma-separated if multiple |
| DATABASE_URL | postgres://user:pass@host:5432/db | From Azure PostgreSQL |
| REDIS_URL | rediss://... | From Azure Redis Cache (if used) |
| SCM_DO_BUILD_DURING_DEPLOYMENT | true | Important: Enables Oryx build |
Python Version
Set in Azure Portal β Configuration β General Settings:
- Stack: Python
- Version: 3.12 (or your Python version)
Problem 1: GLIBC Compatibility Error
Symptom: ImportError: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found
Cause: Pre-building Python packages (especially cryptography) in GitHub Actions creates binaries incompatible with Azure's runtime.
Solution:
- β Let Azure Oryx build Python dependencies on deployment
- β Don't pre-build virtualenv in GitHub Actions
- β Don't use WEBSITE_RUN_FROM_PACKAGE=1 with pre-built packages
Problem 2: Static Files Not Loading (404 errors)
Symptom: CSS/JS return 404, page has no styling
Cause: Django doesn't serve static files in production without WhiteNoise
Solutions:
- Install WhiteNoise: pip install whitenoise
- Add middleware (see configuration above)
- Update STORAGES configuration
- Ensure .oryx-config.yml runs collectstatic
- Verify frontend builds before deployment
Problem 3: Module 'daphne' Not Found
Symptom: ModuleNotFoundError: No module named 'daphne'
Cause: Missing dependency when using Django Channels
Solution: Add to requirements.txt: daphne==4.1.2
Problem 4: App Won't Start After Deployment
Symptoms:
- HTTP 503 errors
- "Application Error" page
- Container fails to start
Debug Steps:
- Download logs: az webapp log download --name your-app --resource-group your-rg --log-file logs.zip
- Check for errors in: - default_docker.log - Startup errors - docker.log - Build errors
- Common causes: - Missing dependencies in requirements.txt - Invalid startup command - Database connection issues - Missing environment variables
π Verification Checklist
After deployment, verify:
- App homepage loads: https://your-app.azurewebsites.net
- Static files load (check CSS/JS in browser DevTools)
- Admin panel loads: https://your-app.azurewebsites.net/admin/
- Database connection works (try logging in)
- No errors in Azure logs
π How This Works
Deployment Flow
- Git Push to main branch β
- GitHub Actions triggered ββ Checkout code ββ Build frontend (npm run build β static/) ββ Create deployment.zip (source + built frontend) β
- Deploy to Azure β
- Azure App Service receives zip ββ Oryx detects Python app ββ Creates virtualenv ββ Installs from requirements.txt (CORRECT GLIBC) ββ Runs post-build (.oryx-config.yml) ββ collectstatic copies files to static_root/ β
- App starts ββ Runs migrations ββ Starts gunicorn β
- WhiteNoise serves static files from static_root/
Why This Approach Works
- Azure Oryx builds Python packages with correct GLIBC version
- WhiteNoise serves static files directly from Django (no separate web server needed)
- Frontend built in CI before deployment (fast, consistent)
- collectstatic runs once during deployment (not on every startup)
- Migrations auto-run via startup command (always up-to-date)
π Additional Resources
- https://docs.djangoproject.com/en/stable/howto/deployment/checklist/
- http://whitenoise.evans.io/
- https://learn.microsoft.com/en-us/azure/app-service/configure-language-python
- https://github.com/microsoft/Oryx
π‘ Pro Tips
- Use django-environ for environment variables: import environ env = environ.Env() SECRET_KEY = env('SECRET_KEY')
- Enable Application Insights for monitoring and debugging
- Set up staging slots for testing before production
- Use Azure Key Vault for sensitive configuration
- Configure custom domain and SSL certificate
- Set up automated backups for your database
π€ Contributing
Found an issue or improvement? This is a living document. Feel free to:
- Fork and improve
- Share your experiences in comments
- Link to this gist in your projects
π Version History
- v1.0 (2025-10-10): Initial release
- Covers Django 4.x/5.x deployment
- WhiteNoise configuration
- GLIBC compatibility solutions
- GitHub Actions CI/CD
β Star this gist if it helped you!
π Encountered issues? Leave a comment below with:
- Django version
- Python version
- Error message
- What you've tried
This guide was created after successfully deploying multiple Django applications to Azure App Service and solving common deployment pitfalls.