Settings and Configuration

Introduction

Production apps read secrets and options from the environment, not hardcoded strings. Pydantic Settings loads and validates configuration from .env files and environment variables. This chapter sets up Settings, exposes them via Depends, and separates development from production values.

Prerequisites

Define Settings

app/core/config.py:

Code explanation:

  • BaseSettings reads fields from environment (case-insensitive)
  • env_file=".env" loads local file for development
  • @lru_cache on get_settings avoids reparsing env every request

.env File

Create .env (gitignored):

env
APP_NAME=Hello FastAPI
ENVIRONMENT=development
DEBUG=true
SECRET_KEY=your-long-random-string
DATABASE_URL=sqlite:///./dev.db
ACCESS_TOKEN_EXPIRE_MINUTES=60

Field names map to env vars: secret_keySECRET_KEY.

.env.example for teammates:

env
SECRET_KEY=replace-me
DATABASE_URL=sqlite:///./app.db
DEBUG=false

Warning

Never Commit .env

Rotate secrets if they ever leak into Git history.

See Git .gitignore and Flask configuration.

Inject Settings in Routes

python
from fastapi import Depends, FastAPI
from app.core.config import Settings, get_settings
 
app = FastAPI()
 
@app.get("/info")
def app_info(settings: Settings = Depends(get_settings)):
    return {
        "app_name": settings.app_name,
        "debug": settings.debug,
    }

Do not return secret_key to clients.

Environment-Specific Values

Option 1 — separate env files:

python
model_config = SettingsConfigDict(env_file=".env.production")

Option 2 — use the environment field on Settings (see above):

Deploy production with:

bash
export ENVIRONMENT=production
export SECRET_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"
export DATABASE_URL="mysql+pymysql://user:pass@localhost/app?charset=utf8mb4"

Validate Configuration at Startup

python
from contextlib import asynccontextmanager
from fastapi import FastAPI
 
@asynccontextmanager
async def lifespan(app: FastAPI):
    settings = get_settings()
    if settings.is_production and settings.secret_key.startswith("dev-"):
        raise RuntimeError("Set a strong SECRET_KEY in production")
    yield
 
app = FastAPI(lifespan=lifespan)

Fail fast on misconfiguration before accepting traffic.

Testing Settings

python
from app.core.config import Settings, get_settings
 
def get_settings_override():
    return Settings(
        secret_key="test-secret",
        database_url="sqlite:///:memory:",
        debug=True,
    )
 
app.dependency_overrides[get_settings] = get_settings_override

Or set env vars before importing app in pytest—see testing chapter.

Common Settings Fields

FieldPurpose
secret_keySigning JWT and sessions
database_urlSQLAlchemy engine URL
cors_originsList of allowed frontend origins (parse as JSON or comma-separated)
openapi_urlSet None to disable /docs in production

Parse list from env:

python
from pydantic import field_validator
 
class Settings(BaseSettings):
    cors_origins: list[str] = ["http://localhost:5173"]
 
    @field_validator("cors_origins", mode="before")
    @classmethod
    def split_origins(cls, v):
        if isinstance(v, str):
            return [x.strip() for x in v.split(",") if x.strip()]
        return v

Env: CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173

FAQ

Settings not loading from .env?

Ensure env_file path is correct relative to working directory when starting Uvicorn.

Field name vs env var?

Pydantic Settings accepts SECRET_KEY for secret_key automatically.

Override .env with export?

Environment variables take precedence over .env file values.

pydantic-settings version?

Use with Pydantic v2—pin compatible versions in requirements.txt.

Store JSON in env?

Use model_validate_json or parse in validator—keep secrets out of repo.

Same as Flask config classes?

Same 12-factor idea—Flask uses dict/classes; FastAPI uses typed Settings.