— Guides
How to Deploy a Django App to Europe: Complete Production Guide
Step-by-step guide to deploying a Django application to European servers for GDPR compliance. Covers production settings, PostgreSQL, Redis, Celery, static files, SSL, and monitoring.
How to Deploy a Django App to Europe: Complete Production Guide
Django is the most popular Python web framework for building serious web applications. But setting up a production deployment — especially in Europe with GDPR requirements — involves more than running the development server.
This guide covers everything: from production settings to PostgreSQL setup, Redis, Celery workers, static files, SSL, and monitoring. By the end, your Django app will be running reliably in a European data center.
Prerequisites
- Django application with a `requirements.txt`
- Git repository (GitHub, GitLab, or Bitbucket)
- Domain name (optional for initial deployment)
Step 1: Production Django Settings
Never use `DEBUG=True` in production. Create a proper production settings file:
```python
settings/production.py
import os
import dj_database_url
DEBUG = False
Security: always set this to your actual domain
ALLOWED_HOSTS = [os.environ['ALLOWED_HOSTS']]
Database: use DATABASE_URL environment variable
DATABASES = {
'default': dj_database_url.config(
default=os.environ['DATABASE_URL'],
conn_max_age=600,
ssl_require=True # Always use SSL in production
)
}
Static files
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
WhiteNoise for serving static files efficiently
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Security headers
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Redis for caching and Celery
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': os.environ['REDIS_URL'],
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
Email (use a transactional email service in production)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.sendgrid.net')
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')
Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'root': {
'handlers': ['console'],
'level': 'WARNING',
},
'loggers': {
'django': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'WARNING'),
'propagate': False,
},
},
}
```
Step 2: Required Dependencies
Add these to your `requirements.txt`:
```text
Django>=5.0
gunicorn>=21.0 # Production WSGI server
psycopg2-binary>=2.9 # PostgreSQL adapter
dj-database-url>=2.0 # Parse DATABASE_URL
whitenoise>=6.6 # Static file serving
django-redis>=5.4 # Redis cache backend
celery>=5.3 # Task queue
redis>=5.0 # Redis client for Celery
```
Step 3: Gunicorn Configuration
Django's development server is not suitable for production. Gunicorn is the standard WSGI server:
```python
gunicorn.conf.py
import multiprocessing
Workers: 2-4 per CPU core
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync' # Use 'gevent' for async views
timeout = 30
keepalive = 2
Logging
accesslog = '-' # stdout
errorlog = '-' # stderr
loglevel = 'warning'
Performance
max_requests = 1000
max_requests_jitter = 100
preload_app = True
```
Step 4: Static Files with WhiteNoise
For a single-dyno or simple deployment, WhiteNoise serves static files efficiently without a separate CDN:
```python
In settings/production.py (already included above)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # Add this second
# ... rest of middleware
]
```
Collect static files before deployment:
```bash
python manage.py collectstatic --noinput
```
Step 5: Database Migrations
Run migrations as part of your deployment process:
```bash
python manage.py migrate --noinput
```
Never run migrations manually in production — automate them in your deployment pipeline.
Step 6: Environment Variables
Create a `.env` file locally (never commit this) and set environment variables in your deployment platform:
```bash
Required
SECRET_KEY=your-very-long-random-secret-key-here
DATABASE_URL=postgresql://user:password@host:5432/dbname
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
DEBUG=False
Redis
REDIS_URL=redis://localhost:6379/0
Email (if using SendGrid)
EMAIL_HOST_USER=apikey
EMAIL_HOST_PASSWORD=your-sendgrid-api-key
Django settings module
DJANGO_SETTINGS_MODULE=myproject.settings.production
```
Generate a strong SECRET_KEY:
```python
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
```
Step 7: Procfile (for Obtura or Heroku-style deployments)
```
web: gunicorn myproject.wsgi:application --config gunicorn.conf.py
worker: celery -A myproject worker -l warning
beat: celery -A myproject beat -l warning --scheduler django_celery_beat.schedulers:DatabaseScheduler
```
Step 8: Celery Configuration (if using background tasks)
```python
myproject/celery.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.production')
app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
Celery settings (add to settings/production.py)
CELERY_BROKER_URL = os.environ['REDIS_URL']
CELERY_RESULT_BACKEND = os.environ['REDIS_URL']
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'UTC'
Important: limit task time to prevent runaway tasks
CELERY_TASK_SOFT_TIME_LIMIT = 300 # 5 minutes
CELERY_TASK_TIME_LIMIT = 360 # 6 minutes
```
Step 9: Deploying with Obtura
With Obtura, deployment is triggered by a git push. Obtura automatically:
- Detects Django from your `requirements.txt`
- Provisions a PostgreSQL database in Germany
- Provisions Redis for caching and Celery
- Runs `collectstatic` and `migrate`
- Configures SSL automatically
```bash
Connect your repository to Obtura
Push to deploy
git push origin main
Obtura handles:
- pip install -r requirements.txt
- python manage.py collectstatic
- python manage.py migrate
- Starts gunicorn web process
- Starts celery worker (if Procfile includes it)
```
Step 10: Health Check Endpoint
Add a health check endpoint so your deployment platform can verify the app is running:
```python
urls.py
from django.http import JsonResponse
def health_check(request):
return JsonResponse({'status': 'ok', 'service': 'django'})
urlpatterns = [
path('health/', health_check, name='health'),
# ... rest of urls
]
```
Step 11: Django Security Checklist
Run Django's built-in security check:
```bash
python manage.py check --deploy
```
This checks for common security issues and will flag anything you've missed.
Step 12: Monitoring
For production, you need to know when things go wrong:
Error tracking: Django integrates with Sentry or Obtura's built-in error tracking:
```python
requirements.txt
sentry-sdk[django]>=1.40
settings/production.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
sentry_sdk.init(
dsn=os.environ.get('SENTRY_DSN', ''),
integrations=[DjangoIntegration()],
traces_sample_rate=0.1, # 10% of requests for performance monitoring
send_default_pii=False # GDPR: don't send PII to Sentry
)
```
GDPR Considerations for Django Apps
Session Data
Django stores session data in the database or cache. This contains personal data:
```python
settings/production.py
Use Redis for sessions (faster, and easier to manage retention)
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
SESSION_COOKIE_AGE = 86400 * 30 # 30 days — match your privacy policy
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
```
Log Minimization
Avoid logging personal data:
```python
Custom logging filter to redact personal data
class RedactEmailFilter(logging.Filter):
def filter(self, record):
if hasattr(record, 'email'):
record.email = '[REDACTED]'
return True
```
Database Encryption
Django doesn't encrypt database fields by default. For sensitive fields:
```bash
pip install django-encrypted-model-fields
```
```python
from encrypted_model_fields.fields import EncryptedEmailField
class UserProfile(models.Model):
# Encrypted at rest
private_notes = EncryptedCharField(max_length=1000, blank=True)
```
Common Deployment Mistakes
Mistake 1: `DEBUG=True` in production — this exposes your settings and detailed error pages to anyone
Mistake 2: Hardcoded `SECRET_KEY` in code — rotate this immediately if it's been committed
Mistake 3: No database connection pooling — Django opens a new database connection per request by default; use pgBouncer or `CONN_MAX_AGE`
Mistake 4: Static files served by Gunicorn — add WhiteNoise or use a CDN
Mistake 5: No health check — deployment platforms can't detect if your app crashed
Production Deployment Checklist
- [ ] `DEBUG=False`
- [ ] Strong `SECRET_KEY` in environment variable
- [ ] `ALLOWED_HOSTS` configured correctly
- [ ] Database using SSL (`ssl_require=True`)
- [ ] Static files collected and served correctly
- [ ] Migrations running as part of deploy
- [ ] HTTPS enforced (`SECURE_SSL_REDIRECT=True`)
- [ ] HSTS configured
- [ ] Error monitoring configured
- [ ] Health check endpoint
- [ ] `python manage.py check --deploy` passes with no issues
Getting Django to production in Europe doesn't need to be a weeks-long project. With the right platform, your app can be live in Germany within minutes.
Deploy your Django app to Germany with Obtura — zero-config, GDPR-compliant, live in under 10 minutes.
— Continue Reading
Related Articles
Comparisons
Vercel vs Obtura: Which Deployment Platform is Better for European Teams?
Vercel is great for frontend teams, but European SMEs face GDPR risks, US data residency, and unpredictable bills. Here's a detailed comparison to help you choose.
Product Updates
Obtura Platform: In the Final Stages of MVP Development
We're in the final stages of developing a viable MVP that solves 80% of existing deployment pipeline challenges for European SMEs. Here's what's coming.
Guides
How to Deploy a Next.js App to Germany with GDPR Compliance (Step-by-Step)
A complete guide to deploying your Next.js application to German servers with full GDPR compliance. Learn what GDPR requires for hosting, and how to set it up correctly.
— Get started
Ready to simplify your DevOps?
Join European SMEs shipping code 3x faster with Obtura's zero-DevOps platform.