Key Takeaways
os.environ.get() calls with type-validated, auto-loaded configuration that fails fast at startup — not silently at runtime in productionDjango’s settings.py is the most dangerous file in your project. Every os.environ.get('SECRET_KEY') call is a silent failure waiting to happen: if the variable is missing, it returns None; if the type is wrong, you find out in production, not at startup. Pydantic’s BaseSettings class eliminates this entire class of bugs by validating every configuration value against its declared type before your application starts. If something is wrong, you get a clear error message immediately — not a cryptic traceback hours later.
This guide covers the complete migration from traditional Django settings to Pydantic-powered configuration, with production patterns we use across every Django project at Boundev.
The Problem with Traditional Django Settings
Traditional Django settings management creates four recurring failure patterns that Pydantic eliminates entirely:
Setting Up Pydantic BaseSettings for Django
The migration from traditional settings to Pydantic involves three steps: installing the dependency, defining your settings model, and mapping it to Django's expected variables. Here is the complete process:
1Install pydantic-settings
Run pip install pydantic-settings. This provides BaseSettings which was moved from core Pydantic to a separate package in v2. It handles environment variable loading, .env file parsing, and type validation.
2Define Your Settings Model
Create a class inheriting from BaseSettings with type-hinted fields for every configuration value: SECRET_KEY: str, DEBUG: bool = False, DATABASE_URL: PostgresDsn. Pydantic reads environment variables matching field names automatically.
3Configure .env File Loading
Add a model_config = SettingsConfigDict(env_file='.env') inner config to load variables from .env files for local development. Environment variables take precedence over .env values, following 12-Factor principles.
4Map to Django Settings
Instantiate your settings model and assign its attributes to Django's expected module-level variables: settings = AppSettings(), then SECRET_KEY = settings.SECRET_KEY. If any required variable is missing or invalid, the app crashes immediately with a descriptive error.
Type-Hinted Configuration Fields
Pydantic provides specialized types for common configuration patterns. Using these instead of raw strings adds both validation and self-documentation to your settings:
Engineering Insight: When our dedicated teams build Django applications, every project starts with a Pydantic settings model. We define custom validators for business-specific constraints (e.g., ensuring CACHE_TTL is between 60 and 3600 seconds) and use computed fields for derived settings (e.g., constructing DATABASES dict from DATABASE_URL). This catches configuration errors before code review, not after deployment.
Building Django Applications?
Boundev provides senior Django developers through staff augmentation who build production-grade Python applications with Pydantic configuration, type-safe codebases, CI/CD pipelines, and 12-Factor compliance from day one.
Talk to Our TeamAdvanced Patterns: Validators and Computed Fields
Pydantic goes beyond simple type checking with custom validators that enforce business rules and computed fields that derive settings from other settings:
Custom Validators
Computed Settings
@computed_field or @model_validator12-Factor Compliance
The 12-Factor App methodology mandates storing configuration in the environment, not in code. Pydantic BaseSettings implements this principle natively, with configurable precedence that supports development convenience without sacrificing production security:
Config in Environment—Pydantic reads environment variables by default. Every setting is externalized from code, making the same artifact deployable across dev, staging, and production.
Secrets Management—SecretStr prevents secret values from appearing in logs, repr(), or error traces. Combine with AWS SSM, GCP Secret Manager, or Vault for production secret injection.
Dev/Prod Parity—the same settings model runs everywhere. .env files provide local overrides; production uses system environment variables. No settings_dev.py vs settings_prod.py split.
Precedence Control—environment variables override .env files, which override default values. This hierarchy is configurable and follows the 12-Factor pattern for deployment flexibility.
Common Mistakes vs Best Practices
What Fails:
os.environ.get() with silent None defaults for required settingsWhat Converts:
FAQ
Why use Pydantic instead of django-environ for Django settings?
While django-environ handles environment variable loading and basic type casting, Pydantic provides a fundamentally more robust configuration system. Pydantic offers full type validation with clear error messages at startup, custom validators for business constraints, computed fields for derived settings, SecretStr for preventing accidental secret exposure, IDE auto-completion and type checking through type hints, and a schema that serves as configuration documentation. django-environ solves the environment variable loading problem; Pydantic solves the configuration validation and documentation problem.
How does Pydantic BaseSettings handle .env files?
Pydantic BaseSettings loads .env files when configured with model_config = SettingsConfigDict(env_file='.env'). The loading follows a clear precedence: constructor arguments take highest priority, then system environment variables, then .env file values, then default values defined in the model. This means development teams can use .env files locally while production systems use platform-injected environment variables, without any code changes. Multiple .env files can be specified for layered configuration.
Can you use Pydantic v2 with Django?
Yes. Pydantic v2 works with all current Django versions. The main change from v1 is that BaseSettings moved to a separate package (pydantic-settings), so you install that separately. The API is largely compatible, with some naming changes: model_config = SettingsConfigDict() replaces the inner class Config, and validators use the new @field_validator decorator. At Boundev, our software outsourcing teams standardize on Pydantic v2 for all new Django projects.
How do you handle database configuration with Pydantic?
The recommended pattern is to define a single DATABASE_URL: PostgresDsn field validated as a proper PostgreSQL DSN, then use a @model_validator or @computed_field to construct Django's DATABASES dictionary by parsing the URL components (scheme, host, port, path for database name, user, password). This follows the 12-Factor principle of a single connection string while still producing the nested dictionary format that Django expects. Pydantic validates the DSN format at startup, catching malformed database URLs before they cause connection errors.
What is the 12-Factor App methodology and how does Pydantic support it?
The 12-Factor App is a methodology for building robust, scalable software-as-a-service applications. Its third factor, "Config," mandates that configuration that varies between deployments (database URLs, API keys, feature flags) must be stored in environment variables, not in code. Pydantic BaseSettings implements this natively by reading environment variables automatically, providing .env file support for local development, enforcing type validation that ensures configuration correctness, and supporting the precedence hierarchy (env vars override .env override defaults) that 12-Factor recommends. This eliminates the need for multiple settings files per environment.
