Skip to main content

Overview

The production settings module at bakerydemo/settings/production.py provides a secure, production-ready configuration for deploying the Wagtail Bakery Demo. This guide covers all configuration options and best practices.

Settings Module Structure

The production settings inherit from base settings:
from .base import *  # Imports all base settings

DEBUG = False  # Always False in production
All production-specific configurations override or extend the base settings.

Security Settings

Secret Key

Critical: Always set DJANGO_SECRET_KEY in production. Never use the ephemeral key generated as a fallback.
The secret key is used for cryptographic signing:
# In production.py
if "DJANGO_SECRET_KEY" in os.environ:
    SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
else:
    # Generates ephemeral key with warning
    SECRET_KEY = "".join(
        [random.SystemRandom().choice(string.printable) for i in range(50)]
    )
Generate a secure secret key:
python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
Set in environment:
export DJANGO_SECRET_KEY="your-generated-secret-key-here"

HTTPS and SSL Settings

Production enforces HTTPS by default:
# Force HTTPS redirect (enabled by default)
SECURE_SSL_REDIRECT = True

# Detect HTTPS via proxy headers
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
Environment Variables:
VariableDescriptionDefault
SECURE_HSTS_SECONDSDuration for HSTS header2592000 (30 days)
SECURE_REFERRER_POLICYReferrer policy headerno-referrer-when-downgrade

Allowed Hosts

Configure which domains can serve your application:
# Accept all hostnames (default - NOT recommended for production)
ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "*").split(",")
Production Requirement: Always set DJANGO_ALLOWED_HOSTS to your actual domain(s).
Example configuration:
# Single domain
export DJANGO_ALLOWED_HOSTS=yourdomain.com

# Multiple domains
export DJANGO_ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com,staging.yourdomain.com

Database Configuration

PostgreSQL Connection

The database is configured via the DATABASE_URL environment variable:
# Standard PostgreSQL URL format
DATABASE_URL=postgres://username:password@hostname:5432/database_name

# With SSL (recommended for cloud databases)
DATABASE_URL=postgres://username:password@hostname:5432/database_name?sslmode=require
Connection URL Format:
postgres://[username]:[password]@[hostname]:[port]/[database]
The DATABASE_URL is automatically parsed by dj-database-url (configured in base settings).

Cache Configuration

The production settings support both Redis and local memory caching:
# Prefer TLS connection over standard
REDIS_URL = os.environ.get("REDIS_TLS_URL", os.environ.get("REDIS_URL"))

if REDIS_URL:
    connection_pool_kwargs = {}
    
    if REDIS_URL.startswith("rediss"):
        # Disable certificate validation for Heroku Redis
        connection_pool_kwargs["ssl_cert_reqs"] = None
    
    redis_options = {
        "IGNORE_EXCEPTIONS": True,
        "SOCKET_CONNECT_TIMEOUT": 2,  # seconds
        "SOCKET_TIMEOUT": 2,  # seconds
        "CONNECTION_POOL_KWARGS": connection_pool_kwargs,
    }
    
    CACHES = {
        "default": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": REDIS_URL + "/0",
            "OPTIONS": redis_options,
        },
        "renditions": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": REDIS_URL + "/1",
            "OPTIONS": redis_options,
        },
    }
Redis Environment Variables:
VariableDescriptionPriority
REDIS_TLS_URLSecure Redis connection URL1st (preferred)
REDIS_URLStandard Redis connection URL2nd (fallback)
Redis URL Format:
# Standard Redis
REDIS_URL=redis://hostname:6379

# With password
REDIS_URL=redis://:password@hostname:6379

# TLS/SSL (Heroku)
REDIS_TLS_URL=rediss://:password@hostname:6379
Cache Usage:
  • default: General application caching (sessions, views, etc.)
  • renditions: Wagtail image rendition caching
Using separate Redis databases (0 and 1) allows independent cache clearing for different cache types.

Static Files and Media

Static Files with WhiteNoise

Production uses WhiteNoise for efficient static file serving:
# Add WhiteNoise middleware
MIDDLEWARE.append("whitenoise.middleware.WhiteNoiseMiddleware")

# Use compressed manifest storage
STORAGES["staticfiles"]["BACKEND"] = (
    "whitenoise.storage.CompressedManifestStaticFilesStorage"
)
Features:
  • Compression (gzip, Brotli)
  • Cache-friendly filenames with content hashes
  • Far-future cache headers
  • No CDN required (but compatible)
Static files are collected during Docker build:
RUN DATABASE_URL=postgres://none REDIS_URL=none python manage.py collectstatic --noinput

Media Storage Options

Configure where user-uploaded media files are stored:
Configure AWS S3 for media storage:
if "AWS_STORAGE_BUCKET_NAME" in os.environ:
    AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME")
    AWS_QUERYSTRING_AUTH = False
    INSTALLED_APPS.append("storages")
    STORAGES["default"]["BACKEND"] = "storages.backends.s3boto3.S3Boto3Storage"
    AWS_S3_FILE_OVERWRITE = False
    AWS_DEFAULT_ACL = "private"
Required Environment Variables:
AWS_STORAGE_BUCKET_NAME=your-bucket-name
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_REGION=us-east-1
Optional Variables:
# Custom domain (CDN)
AWS_S3_CUSTOM_DOMAIN=cdn.yourdomain.com

# Specific region
AWS_S3_REGION_NAME=us-west-2

Email Configuration

The default email backend logs emails to console:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

Configure SMTP Email

To send real emails, override with environment variables in your deployment:
# Add to your environment
export EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
export EMAIL_HOST=smtp.gmail.com
export EMAIL_PORT=587
export EMAIL_USE_TLS=True
export EMAIL_HOST_USER=your-email@gmail.com
export EMAIL_HOST_PASSWORD=your-app-password
export DEFAULT_FROM_EMAIL=noreply@yourdomain.com
You’ll need to add these settings to a custom production settings override or use a service like SendGrid, Mailgun, or AWS SES.

Wagtail Admin Base URL

Set the base URL for admin email notifications:
if "PRIMARY_HOST" in os.environ:
    WAGTAILADMIN_BASE_URL = "https://{}".format(os.environ["PRIMARY_HOST"])
export PRIMARY_HOST=yourdomain.com

Search Backend

Configure Elasticsearch for advanced search capabilities:
ELASTICSEARCH_ENDPOINT = os.getenv("ELASTICSEARCH_ENDPOINT", "")

if ELASTICSEARCH_ENDPOINT:
    from elasticsearch import RequestsHttpConnection
    
    WAGTAILSEARCH_BACKENDS = {
        "default": {
            "BACKEND": "wagtail.search.backends.elasticsearch5",
            "HOSTS": [
                {
                    "host": ELASTICSEARCH_ENDPOINT,
                    "port": int(os.getenv("ELASTICSEARCH_PORT", "9200")),
                    "use_ssl": os.getenv("ELASTICSEARCH_USE_SSL", "off") == "on",
                    "verify_certs": os.getenv("ELASTICSEARCH_VERIFY_CERTS", "off") == "on",
                }
            ],
            "OPTIONS": {
                "connection_class": RequestsHttpConnection,
            },
        }
    }
Environment Variables:
ELASTICSEARCH_ENDPOINT=your-elasticsearch-host.com
ELASTICSEARCH_PORT=9200
ELASTICSEARCH_USE_SSL=on
ELASTICSEARCH_VERIFY_CERTS=on

# For AWS Elasticsearch
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1
If Elasticsearch is not configured, Wagtail falls back to the database search backend.

Frontend Cache Purging

Configure Cloudflare cache purging when content is published:
if (
    "FRONTEND_CACHE_CLOUDFLARE_TOKEN" in os.environ
    or "FRONTEND_CACHE_CLOUDFLARE_BEARER_TOKEN" in os.environ
):
    INSTALLED_APPS.append("wagtail.contrib.frontend_cache")
    WAGTAILFRONTENDCACHE = {
        "default": {
            "BACKEND": "wagtail.contrib.frontend_cache.backends.CloudflareBackend",
            "ZONEID": os.environ["FRONTEND_CACHE_CLOUDFLARE_ZONEID"],
        }
    }
Environment Variables:

Basic Authentication

Optionally protect staging sites with HTTP basic authentication:
if os.environ.get("BASIC_AUTH_ENABLED", "false").lower().strip() == "true":
    MIDDLEWARE.insert(0, "baipw.middleware.BasicAuthIPWhitelistMiddleware")
    
    BASIC_AUTH_LOGIN = os.environ.get("BASIC_AUTH_LOGIN", "wagtail")
    BASIC_AUTH_PASSWORD = os.environ.get("BASIC_AUTH_PASSWORD", "wagtail")
    BASIC_AUTH_DISABLE_CONSUMING_AUTHORIZATION_HEADER = True
    
    if "BASIC_AUTH_WHITELISTED_HTTP_HOSTS" in os.environ:
        BASIC_AUTH_WHITELISTED_HTTP_HOSTS = os.environ[
            "BASIC_AUTH_WHITELISTED_HTTP_HOSTS"
        ].split(",")
Environment Variables:
BASIC_AUTH_ENABLED=true
BASIC_AUTH_LOGIN=admin
BASIC_AUTH_PASSWORD=secure-password-here
BASIC_AUTH_WHITELISTED_HTTP_HOSTS=trusted-host.com,admin.example.com
Basic auth is useful for protecting staging environments while allowing whitelisted hosts to bypass authentication.

Logging Configuration

Production logging outputs to console (stdout):
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
    },
    "loggers": {
        "django": {
            "handlers": ["console"],
            "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"),
        },
    },
}
Environment Variables:
# Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
DJANGO_LOG_LEVEL=WARNING
Console logging is ideal for containerized environments where logs are captured by the container orchestration platform.

Complete Environment Variables Reference

Required Variables

VariableDescriptionExample
DJANGO_SECRET_KEYDjango secret keyyour-50-char-secret-key
DATABASE_URLPostgreSQL connection URLpostgres://user:pass@host/db
DJANGO_SETTINGS_MODULESettings modulebakerydemo.settings.production
VariableDescriptionDefault
DJANGO_ALLOWED_HOSTSComma-separated allowed hosts*
REDIS_URL or REDIS_TLS_URLRedis connection URLNone
PRIMARY_HOSTPrimary domain for emailsNone
DJANGO_DEBUGEnable debug modeoff

Optional: AWS Configuration

VariableDescription
AWS_ACCESS_KEY_IDAWS access key
AWS_SECRET_ACCESS_KEYAWS secret key
AWS_REGIONAWS region
AWS_STORAGE_BUCKET_NAMES3 bucket for media
AWS_S3_REGION_NAMES3 region
AWS_S3_CUSTOM_DOMAINCustom domain for S3

Optional: Search Configuration

VariableDescriptionDefault
ELASTICSEARCH_ENDPOINTElasticsearch hostNone
ELASTICSEARCH_PORTElasticsearch port9200
ELASTICSEARCH_USE_SSLUse SSLoff
ELASTICSEARCH_VERIFY_CERTSVerify SSL certificatesoff

Optional: Cache Configuration

VariableDescription
FRONTEND_CACHE_CLOUDFLARE_ZONEIDCloudflare zone ID
FRONTEND_CACHE_CLOUDFLARE_BEARER_TOKENCloudflare API token
FRONTEND_CACHE_CLOUDFLARE_TOKENCloudflare API key (legacy)
FRONTEND_CACHE_CLOUDFLARE_EMAILCloudflare account email

Optional: Security Configuration

VariableDescriptionDefault
SECURE_HSTS_SECONDSHSTS duration in seconds2592000 (30 days)
SECURE_REFERRER_POLICYReferrer policyno-referrer-when-downgrade
BASIC_AUTH_ENABLEDEnable basic authfalse
BASIC_AUTH_LOGINBasic auth usernamewagtail
BASIC_AUTH_PASSWORDBasic auth passwordwagtail
BASIC_AUTH_WHITELISTED_HTTP_HOSTSBypass auth for hostsNone

Optional: Google Cloud

VariableDescription
GS_BUCKET_NAMEGCS bucket name
GS_PROJECT_IDGCP project ID

Optional: Logging

VariableDescriptionDefault
DJANGO_LOG_LEVELDjango log levelINFO

Best Practices

Recommendations:
  1. Use a secrets management service (AWS Secrets Manager, HashiCorp Vault)
  2. Never commit .env files with real credentials to version control
  3. Use different credentials for staging and production
  4. Rotate secrets regularly
  5. Use strong, randomly generated values
Example .env.example for documentation:
DJANGO_SECRET_KEY=change-me
DATABASE_URL=postgres://user:password@localhost/dbname
REDIS_URL=redis://localhost:6379
DJANGO_ALLOWED_HOSTS=yourdomain.com
Additional security measures:
  1. Database: Use SSL connections (?sslmode=require)
  2. Redis: Use TLS connections (REDIS_TLS_URL)
  3. ALLOWED_HOSTS: Never use * in production
  4. SECRET_KEY: Minimum 50 characters, truly random
  5. HSTS: Consider increasing to 1 year after testing
  6. CSP: Consider adding Content Security Policy headers
Optimize production performance:
  1. Redis: Always use Redis cache in production
  2. Database: Use connection pooling
  3. Static Files: WhiteNoise handles this efficiently
  4. Media Files: Use CDN with S3/GCS
  5. Search: Use Elasticsearch for better performance
  6. Caching: Configure image renditions cache separately
Add monitoring:
  1. Application monitoring (New Relic, DataDog)
  2. Error tracking (Sentry)
  3. Log aggregation (Papertrail, Loggly)
  4. Uptime monitoring (Pingdom, UptimeRobot)
  5. Database monitoring (pg:diagnose on Heroku)

Next Steps

Docker Deployment

Deploy with Docker and docker-compose

Heroku Deployment

Deploy to Heroku platform

Configuration Guide

Learn about configuration options

Deployment Overview

Review deployment strategies

Build docs developers (and LLMs) love