← Back to FastAPI map
FastAPI · Advanced

Production

Settings read from the environment, limits on how often someone can call you, and health check endpoints.

deploy

Overview

Getting to production means making configuration, abuse protection, and health explicit. pydantic-settings loads typed configuration from environment variables and .env files and fails at startup if something required is missing. Rate limiting protects expensive and sensitive endpoints, and a health endpoint tells load balancers and orchestrators whether an instance can take traffic.

Key concepts

BaseSettings
Each field is read from an environment variable of the same name and validated like any Pydantic model.
lru_cache on get_settings
Reads the environment once instead of on every request; override the dependency in tests to change settings.
Rate limiting
Counts requests per client key - IP, user, or API key - within a time window. Redis storage shares the counts across workers.
Liveness vs readiness
Liveness says the process is up. Readiness also checks dependencies such as the database before the instance receives traffic.
Workers
Run several processes - uvicorn --workers, or Gunicorn with Uvicorn workers - to use more than one CPU core.

Best practices

  • Never commit secrets; load them from the environment or a secret manager.
  • Run behind a reverse proxy or load balancer that terminates TLS.
  • Turn off --reload and debug output, and hide or protect /docs for internal APIs.

Production checklist

Settings with pydantic-settings

Config
# pip install pydantic-settings from pydantic_settings import BaseSettings, SettingsConfigDict from functools import lru_cache class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") app_name: str = "My API" debug: bool = False database_url: str secret_key: str redis_url: str = "redis://localhost:6379" allowed_origins: list[str] = ["https://rte.dev"] @lru_cache def get_settings() -> Settings: return Settings() # Inject in routes/deps @app.get("/info") async def info(settings: Settings = Depends(get_settings)): return {"app": settings.app_name}

Tip: The inner class Config is the old Pydantic v1 way; v2 uses model_config = SettingsConfigDict(...). The lru_cache decorator means the environment is read once, not on every request.

Rate limiting with slowapi

Rate limit
# pip install slowapi redis from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded limiter = Limiter(key_func=get_remote_address, storage_uri="redis://localhost") app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) @app.post("/auth/login") @limiter.limit("5/minute") # strict brute-force protection async def login(request: Request, form: OAuth2PasswordRequestForm = Depends()): ... @app.get("/api/search") @limiter.limit("60/minute") # relaxed async def search(request: Request): ...

Watch out: slowapi needs the request: Request parameter in the route signature, and the route decorator must sit above @limiter.limit.

Health check endpoint

Health
from sqlalchemy import text import time START_TIME = time.time() @app.get("/health") async def health(): db_ok = True try: async with engine.connect() as conn: await conn.execute(text("SELECT 1")) # SQLAlchemy 2 needs text() except Exception: db_ok = False return { "status": "healthy" if db_ok else "degraded", "uptime_seconds": round(time.time() - START_TIME), "database": "ok" if db_ok else "unreachable", }

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...