Skip to main content

Docker Configuration Strategy

📚 Part of: Docker Guide - Return to Docker overview

Learn Django-CFG's modern configuration approach: environment variables with pydantic-settings for type-safe, production-ready Docker deployments.


Overview

Django-CFG uses pydantic-settings for automatic configuration loading from:

  • Environment variables - Docker, K8s, CI/CD
  • .env files - Local development only
  • Code defaults - Fallback values
Key Benefit

The universal __ notation allows you to configure ANY setting via environment variables, with automatic type conversion and validation. No YAML files, no rebuilds needed.


Configuration Priority

Settings load in this order (highest priority first):

  1. System environment variables - Docker Compose, K8s, etc.
  2. .env file - Local development only (gitignored)
  3. Code defaults - Pydantic Field definitions

Universal __ Notation

How It Works

Any configuration value can be set using SECTION__FIELD format:

# Simple fields
EMAIL__HOST="mail.example.com"
EMAIL__PORT=465
EMAIL__PASSWORD="your-password-here"

# Nested sections
API_KEYS__OPENAI="sk-proj-your-key-here"
API_KEYS__CLOUDFLARE="your-cloudflare-key"

# Deep nesting
PAYMENTS_API_KEYS__NOWPAYMENTS_API_KEY="TV383SB-..."

Type Conversion

Pydantic automatically converts values based on field types:

TypeExample InputConverted Value
booltrue, 1, yesTrue
boolfalse, 0, noFalse
int465465
float3.143.14
strmail.example.com"mail.example.com"
# Boolean examples
EMAIL__USE_SSL=true # → True
EMAIL__USE_TLS=false # → False

# Integer examples
EMAIL__PORT=465 # → 465 (int)
DATABASE__POOL_SIZE=20 # → 20 (int)

# String examples
EMAIL__HOST="mail.example.com" # → "mail.example.com" (str)

File Structure

docker/
├── .env # Development ENV vars (gitignored!)
├── .env.example # Template (in git)
├── .dockerignore # Excludes sensitive files
└── .gitignore # Ignores .env

projects/django/api/environment/
├── loader.py # Pydantic settings loader
├── .env # Local dev config (gitignored!)
└── .env.example # Template (in git)

File Protection

.dockerignore (in /docker/)

# Prevent secrets from entering Docker build context
.env
.env.*
!.env.example

# Don't copy poetry.lock
poetry.lock

.gitignore (in /docker/)

# Persistent volumes
/volumes/*

# Environment files (contains secrets - DO NOT COMMIT)
.env
.env.local
.env.*.local

.gitignore (in /projects/django/api/environment/)

# Environment file with secrets
.env
.env.local

Configuration Files

Development .env File

Location: docker/.env or api/environment/.env

docker/.env
# ═══════════════════════════════════════════════════════════
# Django-CFG Docker Environment Configuration
# ═══════════════════════════════════════════════════════════
# IMPORTANT: This file is gitignored! Never commit it!
# Copy from .env.example and fill in your values
# ═══════════════════════════════════════════════════════════

# === Environment Mode ===
IS_DEV=true

# === Core Django Settings ===
SECRET_KEY="django-cfg-dev-key-change-in-production-min-50-chars"
DEBUG=true

# === Database ===
DATABASE__URL="postgresql://postgres:postgres@postgres:5432/djangocfg"

# === Cache ===
REDIS_URL="redis://redis:6379/0"

# === Email Configuration ===
EMAIL__BACKEND="console"
EMAIL__HOST="localhost"
EMAIL__PORT=587
EMAIL__USE_TLS=true
EMAIL__DEFAULT_FROM="My App <noreply@localhost.dev>"

# === API Keys ===
API_KEYS__OPENAI="sk-proj-your-dev-key-here"
API_KEYS__OPENROUTER="sk-or-v1-your-dev-key-here"
API_KEYS__CLOUDFLARE="your-cloudflare-key"

# === Telegram ===
TELEGRAM__BOT_TOKEN="123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
TELEGRAM__CHAT_ID=-123456789

# === Twilio (optional) ===
# TWILIO__ACCOUNT_SID="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# TWILIO__AUTH_TOKEN="your-auth-token-here"

# === Payments (optional) ===
# PAYMENTS_API_KEYS__NOWPAYMENTS_API_KEY="TV383SB-..."
# PAYMENTS_API_KEYS__NOWPAYMENTS_IPN_SECRET="your-ipn-secret"
# PAYMENTS_API_KEYS__NOWPAYMENTS_SANDBOX_MODE=true

Example Template (.env.example)

Location: docker/.env.example (committed to git)

docker/.env.example
# Django-CFG Docker Environment - Example
# Copy this file to .env and fill in your values

# === Environment Mode ===
IS_DEV=true

# === Core Settings ===
SECRET_KEY="your-secret-key-minimum-50-characters-long"
DEBUG=true

# === Database ===
DATABASE__URL="postgresql://postgres:postgres@postgres:5432/djangocfg"

# === Cache ===
REDIS_URL="redis://redis:6379/0"

# === Email ===
EMAIL__BACKEND="console"
EMAIL__HOST="smtp.example.com"
EMAIL__PORT=587
EMAIL__USERNAME="your-email@example.com"
EMAIL__PASSWORD="your-password"
EMAIL__USE_TLS=true

# === API Keys ===
API_KEYS__OPENAI="your-openai-key"
API_KEYS__OPENROUTER="your-openrouter-key"

# === Telegram ===
TELEGRAM__BOT_TOKEN="your-bot-token"
TELEGRAM__CHAT_ID=0

Docker Compose Integration

docker-compose.yml
version: '3.8'

services:
django:
build: .
env_file:
- .env # Loads environment variables from file
environment:
# Or set directly (overrides .env file)
DATABASE__URL: "postgresql://postgres:postgres@postgres:5432/db"
REDIS_URL: "redis://redis:6379/0"

postgres:
image: postgres:16
environment:
POSTGRES_DB: djangocfg
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres

redis:
image: redis:7-alpine

Configuration Workflows

Local Development (Without Docker)

How it works:

  1. Create .env file in api/environment/
  2. Run python manage.py runserver
  3. Pydantic-settings automatically loads from .env file
  4. System ENV variables override .env values

Setup:

# Copy example
cp api/environment/.env.example api/environment/.env

# Edit with your credentials
vim api/environment/.env

# Run development server
python manage.py runserver

Docker Development

How it works:

  1. Create .env file in docker/ directory
  2. Docker Compose loads it via env_file:
  3. Pydantic-settings reads environment variables
  4. No rebuilds needed - just restart!

Setup:

# Copy example
cp docker/.env.example docker/.env

# Edit with your credentials
vim docker/.env

# Start Docker services
docker compose up -d

# Restart to apply changes
docker compose restart django

Benefits:

  • No secrets in git - .env is gitignored
  • No rebuilds needed - Just restart container
  • Universal syntax - Works for any config value
  • Type-safe - Automatic validation via Pydantic

Docker Production

How it works:

  1. Set environment variables in Docker/K8s
  2. Never use .env files in production
  3. Use secrets managers (AWS Secrets Manager, Vault, etc.)

Docker Compose production:

version: '3.8'

services:
django:
build: .
environment:
# Set from external sources
IS_PROD: "true"
SECRET_KEY: "${SECRET_KEY}" # From host environment
DATABASE__URL: "${DATABASE_URL}"
EMAIL__PASSWORD: "${EMAIL_PASSWORD}"
API_KEYS__OPENAI: "${OPENAI_API_KEY}"

Kubernetes production:

apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: django
env:
- name: IS_PROD
value: "true"
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: django-secrets
key: secret-key
- name: DATABASE__URL
valueFrom:
secretKeyRef:
name: django-secrets
key: database-url
- name: API_KEYS__OPENAI
valueFrom:
secretKeyRef:
name: django-secrets
key: openai-api-key

Configuration Categories

Use Environment Variables For:

All sensitive credentials

API_KEYS__OPENAI="sk-proj-your-key"
EMAIL__PASSWORD="your-smtp-password"
TELEGRAM__BOT_TOKEN="your-bot-token"

Infrastructure URLs

DATABASE__URL="postgresql://user:pass@host:5432/db"
REDIS_URL="redis://redis:6379/0"

Email credentials

EMAIL__HOST="mail.example.com"
EMAIL__PORT=465
EMAIL__USERNAME="hello@example.com"
EMAIL__PASSWORD="your-password"

Third-party services

TELEGRAM__BOT_TOKEN="123456789:ABC..."
TWILIO__AUTH_TOKEN="your-auth-token"
PAYMENTS_API_KEYS__NOWPAYMENTS_API_KEY="TV383SB-..."

Environment-specific values

# Development
API_KEYS__STRIPE="sk_test_..."
DEBUG=true

# Production
API_KEYS__STRIPE="sk_live_..."
DEBUG=false

Use Code Defaults For:

Development defaults

class DatabaseConfig(BaseSettings):
url: str = Field(default="sqlite:///db/default.sqlite3")

Safe fallbacks

class EmailConfig(BaseSettings):
backend: str = Field(default="console")
port: int = Field(default=587)

Feature flags

class FeatureConfig(BaseSettings):
enable_ai: bool = Field(default=True)
enable_payments: bool = Field(default=False)

Adding New Credentials

Step 1: Add Field to Pydantic Model

# api/environment/loader.py
class ApiKeysConfig(BaseSettings):
"""API keys configuration."""

openai: str = Field(default="")
openrouter: str = Field(default="")
your_new_api: str = Field(default="") # Add here

model_config = SettingsConfigDict(
env_prefix="API_KEYS__",
env_nested_delimiter="__",
)

Step 2: Add to .env

# docker/.env
API_KEYS__YOUR_NEW_API="your-actual-api-key-here"

Step 3: Restart Container (No Rebuild!)

docker compose restart django
Fast Updates

No rebuild needed! Configuration changes only require a restart:

# Old way (slow): Rebuild entire image
docker compose build django --no-cache # ❌ 5+ minutes

# New way (fast): Just restart
docker compose restart django # ✅ 10 seconds

Examples

Complete .env for Docker Development

docker/.env
# === Environment Mode ===
IS_DEV=true

# === Core Django Settings ===
SECRET_KEY="django-cfg-dev-key-change-in-production-min-50-chars"
DEBUG=true

# === Database (Docker service name) ===
DATABASE__URL="postgresql://postgres:postgres@postgres:5432/djangocfg"

# === Cache (Docker service name) ===
REDIS_URL="redis://redis:6379/0"

# === Email Configuration ===
EMAIL__BACKEND="smtp"
EMAIL__HOST="mail.spacemail.com"
EMAIL__PORT=465
EMAIL__USERNAME="hello@example.com"
EMAIL__PASSWORD="your-smtp-password"
EMAIL__USE_TLS=false
EMAIL__USE_SSL=true
EMAIL__DEFAULT_FROM="My App <hello@example.com>"

# === API Keys ===
API_KEYS__OPENAI="sk-proj-your-dev-key-here"
API_KEYS__OPENROUTER="sk-or-v1-your-dev-key-here"
API_KEYS__CLOUDFLARE="your-cloudflare-key"
API_KEYS__NGROK="your-ngrok-token"

# === Telegram ===
TELEGRAM__BOT_TOKEN="123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
TELEGRAM__CHAT_ID=-123456789

# === Twilio ===
TWILIO__ACCOUNT_SID="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
TWILIO__AUTH_TOKEN="your-auth-token-here"
TWILIO__WHATSAPP_FROM="+1234567890"
TWILIO__SMS_FROM="+1234567890"

# === Payments ===
PAYMENTS_API_KEYS__NOWPAYMENTS_API_KEY="TV383SB-..."
PAYMENTS_API_KEYS__NOWPAYMENTS_IPN_SECRET="your-ipn-secret"
PAYMENTS_API_KEYS__NOWPAYMENTS_SANDBOX_MODE=true

Docker Compose with .env

docker-compose.yml
version: '3.8'

services:
django:
build:
context: ../projects/django
dockerfile: ../../docker/Dockerfile
env_file:
- .env # Loads all variables from .env file
ports:
- "8000:8000"
depends_on:
- postgres
- redis
volumes:
- ../projects/django:/app

postgres:
image: postgres:16
environment:
POSTGRES_DB: djangocfg
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql/data

redis:
image: redis:7-alpine
volumes:
- redis_data:/data

volumes:
postgres_data:
redis_data:

Environment Detection

Pydantic-settings automatically detects the environment:

# From loader.py
class EnvironmentMode(BaseSettings):
is_test: bool = Field(default=False)
is_dev: bool = Field(default=False)
is_prod: bool = Field(default=False)

@model_validator(mode="after")
def set_default_env(self):
if not any([self.is_test, self.is_dev, self.is_prod]):
self.is_dev = True
return self

Set in .env:

# docker/.env
IS_DEV=true # Development mode
# IS_PROD=true # Production mode
# IS_TEST=true # Testing mode

Security Best Practices

✅ DO

Use .env for development only

# docker/.env (gitignored!)
API_KEYS__STRIPE="sk_test_your-dev-key"
EMAIL__PASSWORD="your-dev-password"

Keep .env out of git

# Already in .gitignore
.env
.env.local
.env.*.local

Use __ notation for clean variables

EMAIL__HOST="mail.example.com"
API_KEYS__OPENAI="sk-proj-..."
DATABASE__URL="postgresql://..."

Use different keys per environment

# Development
API_KEYS__STRIPE="sk_test_..."

# Production
API_KEYS__STRIPE="sk_live_..."

Use secrets managers in production

  • AWS Secrets Manager
  • HashiCorp Vault
  • Kubernetes Secrets
  • Docker Secrets

Create .env.example for team

# Copy .env and replace values with placeholders
cp .env .env.example
# Replace: sk-proj-abc123 → your-openai-key-here

❌ DON'T

Don't commit .env to git

# ❌ BAD
git add docker/.env # Contains secrets!

Don't use production keys in development

# ❌ BAD - Production key in dev
API_KEYS__STRIPE="sk_live_..." # Should be sk_test_...

Don't hardcode secrets in code

# ❌ BAD - Password in code
EMAIL_PASSWORD = "actual-password-here" # Use ENV variable!

Don't use same keys across environments

Development: ✅ sk_test_...
Production: ❌ sk_test_... # Should be sk_live_...

Don't share .env via insecure channels

  • ❌ Email or Slack
  • ✅ Use password manager
  • ✅ Use secure file transfer

Migration from YAML

Old Way (❌)

config.dev.docker.yaml
# Committed to git or gitignored with credentials
api_keys:
openai: "sk-proj-KEY-HERE"

email:
host: "mail.example.com"
password: "password-here"

New Way (✅)

.env (gitignored)
# Simple ENV variables
API_KEYS__OPENAI="sk-proj-KEY-HERE"
EMAIL__HOST="mail.example.com"
EMAIL__PASSWORD="password-here"

Migration Steps

1. Create .env:

cd docker
cp .env.example .env

2. Move credentials from YAML to .env:

# Old YAML:
api_keys:
openai: "sk-proj-abc123"
email:
host: "mail.example.com"
password: "secret"

# New .env:
API_KEYS__OPENAI="sk-proj-abc123"
EMAIL__HOST="mail.example.com"
EMAIL__PASSWORD="secret"

3. Update .gitignore:

# Remove
config.*.docker.yaml
config.*.ignore.yaml

# Keep
.env
.env.*

4. Restart services:

docker compose restart django

Troubleshooting

Check Environment Variables in Container

docker exec django env | grep "__"
# Should show all your SECTION__FIELD variables

Test Specific Value

docker exec django python -c "
from api.environment import env
print('OpenAI Key:', env.api_keys.openai[:20] if env.api_keys.openai else 'Not set')
print('Email Host:', env.email.host)
print('Database URL:', env.database.url)
"

View Loaded Configuration

docker exec django python manage.py shell
>>> from api.environment import env
>>> print(f"Environment: {env.env.env_mode}")
>>> print(f"Debug: {env.debug}")
>>> print(f"Database: {env.database.url}")
>>> print(f"Email Host: {env.email.host}")

Config Not Updating After Changes

Problem: Made changes to .env but config hasn't updated

Solution: Restart container (no rebuild needed!)

docker compose restart django

Wrong Environment Detected

Check environment variable:

docker exec django env | grep "IS_"
# Should show IS_DEV=true or IS_PROD=true

Set explicitly in docker-compose.yml:

services:
django:
environment:
IS_DEV: "true" # Force development mode

Variables Not Loading

Check .env file location:

ls -la docker/.env
# Should exist and be readable

Check docker-compose.yml:

services:
django:
env_file:
- .env # ✅ Correct path
# - ../api/environment/.env # ❌ Wrong path

Verify variable format:

# ✅ Correct (double underscore)
EMAIL__HOST="mail.example.com"

# ❌ Wrong (single underscore)
EMAIL_HOST="mail.example.com"

Summary

Configuration Loading Order:

  1. System ENV variables - Docker Compose, K8s (highest priority)
  2. .env file - Development only
  3. Code defaults - Pydantic Field definitions (lowest priority)

Key Benefits:

  • No secrets in git - .env is gitignored
  • No image rebuilds - Just restart to pick up changes
  • Universal syntax - SECTION__FIELD works for everything
  • Type-safe - Automatic validation via Pydantic
  • Simple - One configuration method for all environments
  • 12-factor app - Environment-based configuration

Quick Reference:

# Add new credential
echo 'API_KEYS__NEW_SERVICE="your-key-here"' >> docker/.env

# Restart to apply
docker compose restart django

# Verify it loaded
docker exec django python -c "from api.environment import env; print(env.api_keys.new_service)"

Next Steps

Set up development environment: Development Setup →

Deploy to production: Production Guide →

Troubleshoot issues: Troubleshooting →


See Also

Docker Guides

Configuration

Security


Last Updated: 2025-10-21 Django-CFG Version: 1.4.52 Configuration System: Pydantic-settings with ENV variables


TAGS: docker, configuration, environment-variables, secrets, pydantic-settings DEPENDS_ON: [docker, pydantic-settings] USED_BY: [development, production, docker-setup]