Settings & Configuration
Understanding the split settings architecture and environment variables
Settings & Configuration¶
Django SmallStack uses a split settings architecture with environment-based configuration. This approach follows security best practices by keeping sensitive values out of your codebase.
Overview¶
config/
├── settings/
│ ├── __init__.py # Empty, makes it a package
│ ├── base.py # Shared settings for all environments
│ ├── development.py # Local development overrides
│ └── production.py # Production-specific settings
├── urls.py
└── wsgi.py
The Three Settings Files¶
base.py - Shared Configuration¶
Contains settings used in all environments:
INSTALLED_APPS- Your Django appsMIDDLEWARE- Request/response processingTEMPLATES- Template configurationAUTH_USER_MODEL- Custom user modelLOGIN_URL,LOGIN_REDIRECT_URL- Authentication URLsSTATIC_URL,MEDIA_URL- Static/media file URLs
When to add settings here: - Settings that don't change between environments - App registrations - Middleware ordering - Template configuration - URL settings
development.py - Local Development¶
Imports everything from base.py and adds development-specific settings:
from .base import *
DEBUG = True
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
# Use SQLite for simplicity
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Print emails to console
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
When to add settings here: - Debug-only features - Local database configuration - Development email backend - Relaxed security settings for testing - Development-only third-party tool configs
production.py - Production Deployment¶
Imports from base.py and adds security-hardened settings:
from .base import *
from decouple import config
DEBUG = False
ALLOWED_HOSTS = config("ALLOWED_HOSTS", cast=lambda v: [s.strip() for s in v.split(",")])
SECRET_KEY = config("SECRET_KEY")
# Security settings
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
When to add settings here: - Security hardening - Production database configuration - Real email backend (SMTP) - HTTPS/SSL settings - Caching configuration - Production logging
Environment Variables with python-decouple¶
Django SmallStack uses python-decouple to read configuration from environment variables and .env files.
Why Use Environment Variables?¶
- Security - Secrets never enter version control
- Flexibility - Change configuration without code changes
- 12-Factor App - Follows modern deployment best practices
- Environment parity - Same code runs in dev and production
How It Works¶
from decouple import config
# Read a string (default type)
SECRET_KEY = config("SECRET_KEY")
# Read with a default value
DEBUG = config("DEBUG", default=False, cast=bool)
# Read an integer
DATABASE_PORT = config("DATABASE_PORT", default=5432, cast=int)
# Read a comma-separated list
ALLOWED_HOSTS = config("ALLOWED_HOSTS", cast=lambda v: [s.strip() for s in v.split(",")])
The .env File¶
Create a .env file in your project root for local development:
# .env - DO NOT COMMIT THIS FILE
# Security
SECRET_KEY=your-secret-key-here
DEBUG=True
# Database (production)
DATABASE_URL=postgres://user:pass@localhost:5432/dbname
# Email
EMAIL_HOST=smtp.gmail.com
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=your-app-password
# Development superuser
DEV_SUPERUSER_USERNAME=admin
DEV_SUPERUSER_PASSWORD=change-me-for-dev
DEV_SUPERUSER_EMAIL=admin@example.com
Configuration Lookup Order¶
python-decouple looks for values in this order:
- Environment variables - System or shell environment
.envfile - In the project root- Default value - Specified in code with
default=
This means:
- In production, set real environment variables (more secure)
- In development, use .env file (convenient)
- Defaults provide fallbacks for optional settings
Adding New Configuration¶
Step 1: Decide Where It Belongs¶
| Setting Type | File | Example |
|---|---|---|
| App registration | base.py |
INSTALLED_APPS |
| Shared behavior | base.py |
AUTH_USER_MODEL |
| Debug features | development.py |
DEBUG_TOOLBAR_CONFIG |
| Security settings | production.py |
SECURE_SSL_REDIRECT |
| Secrets | .env + production.py |
SECRET_KEY, API keys |
| Environment-specific | Both dev & prod | DATABASES, EMAIL_BACKEND |
Step 2: Add the Setting¶
Example: Adding a third-party API key
- Add to
.env:
STRIPE_API_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
- Add to
production.py(orbase.pyif needed everywhere):
from decouple import config
STRIPE_API_KEY = config("STRIPE_API_KEY")
STRIPE_WEBHOOK_SECRET = config("STRIPE_WEBHOOK_SECRET")
- For development with a default:
STRIPE_API_KEY = config("STRIPE_API_KEY", default="sk_test_development_key")
Example: Adding a feature flag
- Add to
.env:
ENABLE_NEW_FEATURE=True
- Add to
base.py:
from decouple import config
ENABLE_NEW_FEATURE = config("ENABLE_NEW_FEATURE", default=False, cast=bool)
- Use in your code:
from django.conf import settings
if settings.ENABLE_NEW_FEATURE:
# New feature code
Example: Adding debug-only settings
Add directly to development.py (no env var needed):
# development.py
# Django Debug Toolbar
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
INTERNAL_IPS = ["127.0.0.1"]
Selecting the Active Settings¶
Method 1: Environment Variable (Recommended)¶
Set DJANGO_SETTINGS_MODULE before running Django:
# Development
export DJANGO_SETTINGS_MODULE=config.settings.development
uv run python manage.py runserver
# Production
export DJANGO_SETTINGS_MODULE=config.settings.production
gunicorn config.wsgi:application
Method 2: In .env File¶
# .env
DJANGO_SETTINGS_MODULE=config.settings.development
Method 3: Command Line Flag¶
uv run python manage.py runserver --settings=config.settings.development
In Docker¶
The Dockerfile and docker-compose.yml set this automatically:
# docker-compose.yml
environment:
- DJANGO_SETTINGS_MODULE=config.settings.production
Security Best Practices¶
Never Commit Secrets¶
Add to .gitignore:
# Environment files
.env
.env.local
.env.production
# But DO commit the example
!.env.example
Use .env.example as Documentation¶
Maintain a .env.example with dummy values:
# .env.example - Safe to commit, shows required variables
SECRET_KEY=change-me-to-a-real-secret-key
DEBUG=False
ALLOWED_HOSTS=localhost,127.0.0.1
DATABASE_URL=postgres://user:password@localhost:5432/dbname
EMAIL_HOST=smtp.example.com
EMAIL_HOST_USER=your-email@example.com
EMAIL_HOST_PASSWORD=your-password
Generate Secure Secret Keys¶
Never use a weak or example secret key in production:
# Generate a new secret key
from django.core.management.utils import get_random_secret_key
print(get_random_secret_key())
Or use an online generator like djecrety.ir.
Validate Required Settings¶
In production.py, ensure critical settings are present:
from decouple import config
# These will raise an error if not set
SECRET_KEY = config("SECRET_KEY") # No default = required
DATABASE_URL = config("DATABASE_URL")
# Explicitly fail if DEBUG is somehow True
DEBUG = config("DEBUG", default=False, cast=bool)
if DEBUG:
raise ValueError("DEBUG must be False in production")
Use Different Secrets Per Environment¶
| Environment | SECRET_KEY |
|---|---|
| Development | Can use a simple key |
| Staging | Unique secure key |
| Production | Unique secure key (different from staging) |
Common Patterns¶
Database Configuration¶
Development (SQLite):
# development.py
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
Production (PostgreSQL with dj-database-url):
# production.py
import dj_database_url
from decouple import config
DATABASES = {
"default": dj_database_url.config(
default=config("DATABASE_URL")
)
}
Email Configuration¶
Development (Console):
# development.py
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
Production (SMTP):
# production.py
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = config("EMAIL_HOST")
EMAIL_PORT = config("EMAIL_PORT", default=587, cast=int)
EMAIL_USE_TLS = config("EMAIL_USE_TLS", default=True, cast=bool)
EMAIL_HOST_USER = config("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD")
Static Files¶
Development:
# development.py - Django serves static files
# No additional configuration needed
Production:
# production.py - Use WhiteNoise or external storage
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
# Or S3
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
AWS_ACCESS_KEY_ID = config("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = config("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = config("AWS_STORAGE_BUCKET_NAME")
Troubleshooting¶
"SECRET_KEY not found"¶
The setting isn't in your environment or .env file:
# Check if it's set
echo $SECRET_KEY
# Or add to .env
echo "SECRET_KEY=your-key-here" >> .env
Wrong Settings File Being Used¶
Check which settings module is active:
# In Django shell
from django.conf import settings
print(settings.SETTINGS_MODULE)
Changes Not Taking Effect¶
- Restart your development server
- Check you're editing the correct settings file
- Verify the environment variable is exported (not just set)
# Wrong (only sets for current line)
DJANGO_SETTINGS_MODULE=config.settings.development python manage.py runserver
# Right (exports for session)
export DJANGO_SETTINGS_MODULE=config.settings.development
python manage.py runserver
Summary¶
| File | Purpose | Secrets? |
|---|---|---|
base.py |
Shared settings | No |
development.py |
Local dev overrides | No |
production.py |
Production settings | Read from env |
.env |
Local secrets | Yes (don't commit) |
.env.example |
Documentation | No (safe to commit) |
Key principles:
1. Secrets go in environment variables, never in code
2. Use base.py for shared settings
3. Use environment-specific files for overrides
4. Always provide .env.example for documentation
5. Validate required settings in production