Back to Blog

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.

22 min read
By Alexandru-Nicolae Șerban
deploy django europedjango production deploymentdjango gdprpython deployment germanydjango postgresql production

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.

— Get started

Ready to simplify your DevOps?

Join European SMEs shipping code 3x faster with Obtura's zero-DevOps platform.