Skip to main content

Production Configuration

Production-ready Django-CFG configuration demonstrating all features, security best practices, and real-world patterns.

Security First

Production configuration requires careful attention to secrets management, SSL/TLS, and security headers. Never commit secrets to version control.

Minimal Production Setup

Production Deployment Checklist

CRITICAL: Before deploying to production, verify ALL of these:

  • 🔒 DEBUG = False - Never run with DEBUG=True in production
  • 🔒 Strong SECRET_KEY - At least 50 random characters
  • 🔒 Reverse proxy SSL - nginx/Cloudflare handles HTTPS
  • 🔒 Secrets from environment - No hardcoded credentials
  • 🔒 Database SSL - Encrypted database connections
  • 🔒 security_domains set - Production domains configured
  • 🔒 Static files collected - Run collectstatic before deploy
Start Simple

Start with minimal configuration and add features as needed. This prevents configuration bloat and reduces attack surface.

config.py
from django_cfg import DjangoConfig, DatabaseConfig
from .environment import env

class ProductionConfig(DjangoConfig):
# Project basics
project_name: str = env.app.name
project_version: str = env.app.version

# Security
secret_key: str = env.secret_key
debug: bool = False
# ssl_redirect: Optional - not specified (defaults to None)
# Assumes reverse proxy (nginx/Cloudflare) handles SSL
security_domains: list[str] = env.security.domains

# Media files - CDN for production
media_url: str = env.media_url # e.g., "https://cdn.example.com/media/"

# Database from environment
databases: dict[str, DatabaseConfig] = {
"default": DatabaseConfig.from_url(url=env.database.url)
}

config = ProductionConfig()

Complete Production Configuration

Configuration Complexity

This complete example shows all available features. For most production deployments, start with the minimal setup above and add features incrementally as needed. Unnecessary features increase attack surface and maintenance burden.

# config.py
from typing import Optional
from django_cfg import (
DjangoConfig,
DatabaseConfig,
CacheConfig,
EmailConfig,
TwilioConfig,
TaskConfig,
UnfoldConfig,
SpectacularConfig,
)
from .environment import env


class MyProductionConfig(DjangoConfig):
"""
Production configuration with all Django-CFG features.

Environment-aware, type-safe, and production-ready.
"""

# === Project Metadata ===
project_name: str = env.app.name
project_version: str = env.app.version or "1.0.0"
project_description: str = env.app.description or ""

# === Security Settings ===
secret_key: str = env.secret_key
debug: bool = env.debug
# ssl_redirect: Optional - not specified (reverse proxy handles SSL)
security_domains: list[str] = env.security.domains

# === Built-in Applications ===
# User management
enable_accounts: bool = True
enable_support: bool = True
enable_newsletter: bool = env.features.marketing
enable_leads: bool = env.features.marketing

# AI features (optional)
enable_knowbase: bool = env.features.ai
enable_agents: bool = env.features.ai

# Maintenance
enable_maintenance: bool = True

# === Custom Applications ===
project_apps: list[str] = [
"core",
"apps.users",
"apps.api",
# Add your apps here
]

# === Database Configuration ===
databases: dict[str, DatabaseConfig] = {
# Primary database
"default": DatabaseConfig.from_url(
url=env.database.url,
sslmode="require" if env.is_production else "prefer",
connect_timeout=10,
),
}

# === Cache Configuration ===
# ✨ AUTO-MAGIC: Just set redis_url - cache auto-created!
redis_url: Optional[str] = env.redis.url
# Django-CFG automatically creates CacheConfig with:
# - timeout=300 (5 minutes)
# - key_prefix=project_name.lower()
# - max_connections=50
# Override with explicit cache_default if needed

# === Email Configuration ===
email: Optional[EmailConfig] = (
EmailConfig(
backend="django.core.mail.backends.smtp.EmailBackend",
host=env.email.host,
port=env.email.port,
user=env.email.user,
password=env.email.password,
use_tls=True,
from_email=env.email.from_email,
)
if env.email.enabled
else None
)

# === SMS/OTP Configuration ===
telegram: Optional[TelegramConfig] = (
TelegramConfig(
bot_token=env.telegram.bot_token,
chat_id=env.telegram.chat_id,
)
if env.telegram.enabled
else None
)

# === Background Tasks ===
tasks: Optional[TaskConfig] = (
TaskConfig(
redis_url=env.redis.url,
processes=env.tasks.processes or 4,
threads=env.tasks.threads or 8,
queues=env.tasks.queues or ["default", "high", "low"],
max_retries=3,
)
if env.redis.url
else None
)

# === Admin Interface ===
unfold: Optional[UnfoldConfig] = UnfoldConfig(
site_title=f"{env.app.name} Admin",
site_header=f"{env.app.name} Administration",
dashboard_enabled=True,
show_search=True,
)

# === API Documentation ===
spectacular: Optional[SpectacularConfig] = SpectacularConfig(
title=f"{env.app.name} API",
description=env.app.description or "API Documentation",
version=env.app.version or "1.0.0",
)

# === API Keys (from environment) ===
api_keys: dict[str, str] = {
"service_a": env.api_keys.service_a or "",
"service_b": env.api_keys.service_b or "",
# Add your API keys here
}


# Create and set configuration
config = MyProductionConfig()

Environment Configuration

NEVER Commit .env Files

Critical security rule: Environment files contain production secrets and MUST NEVER be committed to version control.

Add to .gitignore:

# Environment files
.env
.env.*
env.production
config.prod.yaml
*.secret.yaml

# But keep examples
!.env.example
!config.example.yaml

Instead:

  • ✅ Use .env.example with placeholder values
  • ✅ Document required variables in README
  • ✅ Use secret management systems (AWS Secrets Manager, HashiCorp Vault)
  • ✅ Set environment variables in deployment platform
# environment/config.yaml
app:
name: "My Application"
slug: "myapp"
version: "1.0.0"
description: "Production application"

secret_key: "${SECRET_KEY}"
debug: false

# ssl_redirect: Optional - not needed (reverse proxy handles SSL)

security:
domains:
- "example.com"
- "www.example.com"

database:
url: "${DATABASE_URL}"

redis:
url: "${REDIS_URL}"

email:
enabled: true
host: "smtp.example.com"
port: 587
user: "${EMAIL_USER}"
password: "${EMAIL_PASSWORD}"
from_email: "noreply@example.com"

telegram:
enabled: false
bot_token: "${TELEGRAM_BOT_TOKEN}"
chat_id: "${TELEGRAM_CHAT_ID}"

features:
marketing: true # Enable leads & newsletter
ai: false # Disable AI features in production

tasks:
processes: 4
threads: 8
queues:
- default
- high
- low
- emails
- reports

api_keys:
service_a: "${SERVICE_A_API_KEY}"
service_b: "${SERVICE_B_API_KEY}"

Multi-Environment Setup

Environment Strategy

Use separate configuration classes for each environment to ensure proper isolation and prevent production mistakes.

config.py
class DevelopmentConfig(DjangoConfig):
debug: bool = True
# security_domains: Optional - not needed in development
# Django-CFG auto-configures CORS fully open

# All features enabled for development
enable_accounts: bool = True
enable_support: bool = True
enable_newsletter: bool = True
enable_leads: bool = True
enable_knowbase: bool = True
enable_agents: bool = True

# SQLite for development
databases: dict[str, DatabaseConfig] = {
"default": DatabaseConfig.from_url("sqlite:///dev.db")
}
DEBUG=True is DANGEROUS in Production

Never run with DEBUG=True in production. It exposes:

  • 🔓 Full stack traces - Reveals source code paths and logic
  • 🔓 Environment variables - Shows SECRET_KEY and credentials
  • 🔓 SQL queries - Exposes database structure and data
  • 🔓 Internal paths - File system layout and dependencies
  • 🔓 Library versions - Makes exploit targeting easier

Performance impact:

  • Templates not cached - Every request recompiles
  • Static files served by Django - Slow, not production-ready
  • Query logging enabled - Memory leaks over time
Development Benefits

Development environment enables all features for testing, uses SQLite for simplicity, and disables SSL redirect for local testing.

Configuration Selection

Wrong Environment = Production Disaster

Loading the wrong configuration can cause catastrophic failures:

  • ❌ Development config in production → Exposed debug info, disabled security
  • ❌ Production config in development → Data corruption in wrong database
  • ❌ Missing environment variable → Application crash on startup

Always validate environment selection:

# Fail fast if environment is wrong
if env.environment not in CONFIG_MAP:
raise ValueError(f"Invalid environment: {env.environment}")

# Validate production safeguards
if env.environment == "production" and config.debug:
raise ValueError("DEBUG cannot be True in production!")
# config.py
from django_cfg import DjangoConfig
from .environment import env

# Define all configurations
class DevelopmentConfig(DjangoConfig):
...

class StagingConfig(DjangoConfig):
...

class ProductionConfig(DjangoConfig):
...

# Select configuration based on environment
CONFIG_MAP = {
"development": DevelopmentConfig,
"staging": StagingConfig,
"production": ProductionConfig,
}

# Validate environment
if env.environment not in CONFIG_MAP:
raise ValueError(
f"Invalid environment '{env.environment}'. "
f"Must be one of: {list(CONFIG_MAP.keys())}"
)

config = CONFIG_MAP[env.environment]()

# Final production validation
if env.environment == "production":
if config.debug:
raise ValueError("CRITICAL: DEBUG=True in production!")
if len(config.secret_key) < 50:
raise ValueError("CRITICAL: SECRET_KEY too short for production!")
if not config.security_domains:
raise ValueError("CRITICAL: security_domains not configured!")

Security Best Practices

Critical Security Requirements

Production applications must implement all security best practices below. Skipping any of these increases vulnerability to attacks.

1. Secret Management

import os
from django_cfg import DjangoConfig

class SecureConfig(DjangoConfig):
# Fail fast if missing
secret_key: str = os.environ["SECRET_KEY"]

# Validate at startup
def __post_init__(self):
if len(self.secret_key) < 50:
raise ValueError("SECRET_KEY too short")
if self.debug and "production" in self.environment:
raise ValueError("DEBUG cannot be True in production")
Secret Key Length

Django's SECRET_KEY must be at least 50 characters long and cryptographically random. Use django.core.management.utils.get_random_secret_key() to generate.

2. Database Security

SSL Required

Always use SSL/TLS for database connections in production. Unencrypted connections expose credentials and data.

databases: dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
sslmode="require", # Force SSL
connect_timeout=10, # Timeout protection
options={
"options": "-c statement_timeout=30000", # 30s query timeout
},
)
}

3. CORS & Security Headers

Security Headers

Django-CFG automatically configures security headers when security_domains is set. Manual configuration rarely needed.

class SecureConfig(DjangoConfig):
security_domains: list[str] = [
"myapp.com", # ✅ Flexible format
"https://api.myapp.com", # ✅ With protocol
"www.myapp.com:8443", # ✅ With port
]
# ssl_redirect: Optional - not needed (reverse proxy handles SSL)
cors_allow_headers: list[str] = [
"accept",
"content-type",
"authorization",
]
Auto-configured Security Headers

When security_domains is set, Django-CFG automatically enables:

  • SECURE_SSL_REDIRECT = False - Reverse proxy handles redirects
  • SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') - Trust proxy
  • SECURE_HSTS_SECONDS = 31536000 - Force HTTPS for 1 year
  • SECURE_HSTS_INCLUDE_SUBDOMAINS = True - Apply to subdomains
  • SECURE_CONTENT_TYPE_NOSNIFF = True - Prevent MIME sniffing
  • X_FRAME_OPTIONS = "DENY" - Prevent clickjacking
  • SECURE_BROWSER_XSS_FILTER = True - Enable XSS protection

Media Files Configuration

Production Media Strategy

In production, serve media files from a CDN or cloud storage (S3, CloudFlare R2) for better performance and scalability.

Media URL Options

class ProductionConfig(DjangoConfig):
# Option 1: CDN (recommended for production)
media_url: str = "https://cdn.example.com/media/"

# Option 2: Auto-generate from api_url
media_url: str = "__auto__" # → https://api.example.com/media/

# Option 3: Relative URL (served by Django/nginx)
media_url: str = "/media/" # Default

Environment Configuration

# Production - CDN
MEDIA_URL="https://cdn.example.com/media/"

# Or auto-generate from API URL
MEDIA_URL="__auto__"

CDN Integration

# config.py
media_url: str = "https://media.example.com/uploads/"

# Use django-storages for R2
# DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
# Environment
MEDIA_URL="https://media.example.com/uploads/"
AWS_S3_ENDPOINT_URL="https://xxx.r2.cloudflarestorage.com"
AWS_ACCESS_KEY_ID="your-access-key"
AWS_SECRET_ACCESS_KEY="your-secret-key"
Django Serves Media Only in Development

When media_url is an absolute URL (starts with http:// or https://), Django-CFG automatically skips adding local media serving routes. Media files must be served by your CDN or reverse proxy.

Performance Optimization

Performance Best Practices

Proper configuration of database pooling, caching, and task queues is critical for production performance at scale.

config.py
databases: dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
options={
"MAX_CONNS": 20, # Maximum connections
"MIN_CONNS": 5, # Minimum idle connections
},
connect_timeout=10, # Connection timeout
)
}
Connection Pool Sizing

Rule of thumb: MAX_CONNS = (CPU cores × 2) + effective spindle count

For cloud databases, limit connections to avoid quota exhaustion.

Advanced Performance Tuning

Read Replicas

databases: dict[str, DatabaseConfig] = {
"default": DatabaseConfig.from_url(
env.database.url,
operations=["write", "migrate"],
),
"read_replica_1": DatabaseConfig.from_url(
env.database.replica_1_url,
operations=["read"],
),
"read_replica_2": DatabaseConfig.from_url(
env.database.replica_2_url,
operations=["read"],
),
}

Cache Layering

# Multiple cache backends
CACHES = {
"default": { # Redis for session/general
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": env.redis.url,
},
"local": { # Local memory for hot data
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "unique-snowflake",
},
}

Monitoring & Logging

Production Observability

Production systems require comprehensive logging, monitoring, and alerting to ensure reliability and quick incident response.

config.py
class MonitoredConfig(DjangoConfig):
# Startup information
startup_info_mode: str = "SHORT" # Minimal logs in production

# Telegram notifications
telegram: TelegramConfig = TelegramConfig(
bot_token=env.telegram.bot_token,
chat_id=env.telegram.admin_chat,
)

# Custom logging
@property
def logging(self) -> dict:
return {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{levelname} {asctime} {module} {message}",
"style": "{",
},
},
"handlers": {
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "/var/log/myapp/django.log",
"maxBytes": 10485760, # 10MB
"backupCount": 5,
"formatter": "verbose",
},
"telegram": {
"class": "myapp.logging.TelegramHandler",
"bot_token": self.telegram.bot_token,
"chat_id": self.telegram.chat_id,
"level": "ERROR", # Only errors to Telegram
},
},
"loggers": {
"django": {
"handlers": ["file", "telegram"],
"level": "INFO",
},
"django.request": {
"handlers": ["file", "telegram"],
"level": "ERROR",
"propagate": False,
},
},
}
Log Rotation

Always use RotatingFileHandler or TimedRotatingFileHandler in production to prevent disk space exhaustion.

See Also

Deployment & Production

Production Setup:

Configuration:

Security & Performance

Security:

Performance:

Features & Integration

Built-in Features:

Integration:

Tools & Support

CLI Tools:

Support:

Note: All examples use YAML-based configuration. See Configuration Guide for complete setup.