FastAPI Settings and Environment Variables
This setup creates a clean configuration pattern for FastAPI projects. Settings are loaded from environment variables, .env files, and Docker Compose values, then exposed through a typed settings object used by the application, database layer, CORS setup, and authentication configuration.
Project structure
Section titled “Project structure”Create a separate project folder for the settings and environment configuration example. This project keeps configuration in app/config.py and uses .env files for local values.
07-settings-environment/ app/ __init__.py config.py database.py main.py .env .env.example requirements.txt Dockerfile compose.yamlmkdir 07-settings-environmentcd 07-settings-environmentmkdir apptouch app/__init__.pyCreate the example environment file
Section titled “Create the example environment file”Create .env.example as the safe reference file that can be committed to source control. It documents all required settings without storing real secrets.
.env.exampleAPP_NAME=FastAPI Settings ExampleENVIRONMENT=developmentDEBUG=true
DATABASE_URL=sqlite:////data/app.db
SECRET_KEY=change-this-dev-secretACCESS_TOKEN_EXPIRE_MINUTES=30
CORS_ORIGINS=http://localhost:3000,http://localhost:8000Create the local environment file
Section titled “Create the local environment file”Create .env for local development values. This file can contain secrets and should not be committed to source control.
.envAPP_NAME=FastAPI Settings ExampleENVIRONMENT=developmentDEBUG=true
DATABASE_URL=sqlite:////data/app.db
SECRET_KEY=local-dev-secret-valueACCESS_TOKEN_EXPIRE_MINUTES=30
CORS_ORIGINS=http://localhost:3000,http://localhost:8000Add .env to .gitignore in real projects so local secrets do not enter the repository.
.gitignore.env__pycache__/.pytest_cache/*.dbCreate the settings object
Section titled “Create the settings object”Create the settings file at app/config.py. This file loads environment values into one validated Settings object that the rest of the app can import.
app/config.pyfrom functools import lru_cache
from pydantic import Field, field_validatorfrom pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore", )
app_name: str = "FastAPI Settings Example" environment: str = "development" debug: bool = False
database_url: str = "sqlite:////data/app.db"
secret_key: str = Field(min_length=16) access_token_expire_minutes: int = Field(default=30, ge=1)
cors_origins: list[str] = []
@field_validator("cors_origins", mode="before") @classmethod def parse_cors_origins(cls, value): if isinstance(value, str): return [ item.strip() for item in value.split(",") if item.strip() ]
return value
@property def is_development(self): return self.environment == "development"
@property def is_production(self): return self.environment == "production"
@lru_cachedef get_settings(): return Settings()The lru_cache wrapper keeps one settings instance instead of rebuilding it on every import or request.
Create the database connection
Section titled “Create the database connection”Create the database setup file at app/database.py. The database URL comes from the shared settings object instead of being hardcoded in the database file.
app/database.pyfrom sqlalchemy import create_enginefrom sqlalchemy.orm import declarative_base, sessionmaker
from app.config import get_settings
settings = get_settings()
connect_args = {}
if settings.database_url.startswith("sqlite"): connect_args = { "check_same_thread": False }
engine = create_engine( settings.database_url, connect_args=connect_args,)
SessionLocal = sessionmaker( autocommit=False, autoflush=False, bind=engine,)
Base = declarative_base()
def get_db(): db = SessionLocal() try: yield db finally: db.close()Create the FastAPI app
Section titled “Create the FastAPI app”Create the main application file at app/main.py. The app reads settings for the application title, debug flag, CORS origins, and safe runtime configuration output.
app/main.pyfrom fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddleware
from app.config import get_settings
settings = get_settings()
app = FastAPI( title=settings.app_name, debug=settings.debug,)
app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"],)
@app.get("/")def home(): return { "message": "FastAPI settings environment example", "environment": settings.environment, }
@app.get("/config/public")def public_config(): return { "app_name": settings.app_name, "environment": settings.environment, "debug": settings.debug, "cors_origins": settings.cors_origins, "access_token_expire_minutes": settings.access_token_expire_minutes, }
@app.get("/health")def health(): return { "status": "ok" }The public config route intentionally excludes SECRET_KEY and DATABASE_URL. Secrets should not be returned from API routes.
Add dependencies
Section titled “Add dependencies”Create the dependency file at requirements.txt. pydantic-settings provides the BaseSettings class for environment based configuration.
requirements.txtfastapiuvicorn[standard]sqlalchemypydantic-settingsCreate the Dockerfile
Section titled “Create the Dockerfile”Create the Dockerfile at the project root. The container owns the Python environment and runs the FastAPI app with Uvicorn.
DockerfileFROM python:3.12-slim
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"ENV PYTHONDONTWRITEBYTECODE=1ENV PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --upgrade pipRUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]Create the Compose file
Section titled “Create the Compose file”Create the Docker Compose file at compose.yaml. The env_file loads .env values into the backend container, and the environment block can override specific values when needed.
compose.yamlservices: backend: build: . container_name: fastapi-07-settings-environment ports: - "8000:8000" env_file: - .env environment: DATABASE_URL: sqlite:////data/app.db volumes: - .:/app - settings_data:/data
volumes: settings_data:When the same setting exists in both env_file and environment, the explicit environment value takes priority inside the container.
Run the project
Section titled “Run the project”Run Docker Compose from inside the project folder. The app starts with values loaded from .env and Docker Compose.
docker compose up --buildOpen the public config route to verify which safe settings are active.
http://localhost:8000/config/public{ "app_name": "FastAPI Settings Example", "environment": "development", "debug": true, "cors_origins": [ "http://localhost:3000", "http://localhost:8000" ], "access_token_expire_minutes": 30}Override a setting from Docker Compose
Section titled “Override a setting from Docker Compose”Override a setting temporarily from the shell without editing .env. This is useful for quick checks or CI style commands.
docker compose run --rm -e ENVIRONMENT=staging -e DEBUG=false backend python -c "from app.config import get_settings; s = get_settings(); print(s.environment, s.debug)"The command should print the overridden values from the container process.
staging FalseUse separate environment files
Section titled “Use separate environment files”For larger projects, keep separate files for local, test, and production style values. Commit only safe example files and avoid committing real secrets.
.env.example.env.local.env.test.env.production.exampleRun Compose with a different environment file when needed.
docker compose --env-file .env.test up --buildValidate required settings
Section titled “Validate required settings”Settings validation should fail early when required values are missing or invalid. For example, secret_key requires at least 16 characters and access_token_expire_minutes must be greater than or equal to 1.
.envSECRET_KEY=shortACCESS_TOKEN_EXPIRE_MINUTES=0Starting the app with invalid settings should fail during startup instead of failing later during authentication or runtime behavior.
Production settings pattern
Section titled “Production settings pattern”In production, provide secrets through the deployment environment rather than committing them in files. Docker, CI systems, cloud services, and secret managers can inject environment variables at runtime.
Development .env file is acceptable
Repository commit .env.example only
Production inject real values from environment or secret manager
Never expose SECRET_KEY DATABASE_URL with credentials API keys OAuth client secretsSettings mental model
Section titled “Settings mental model”A settings object keeps environment access in one place. Application files import settings instead of calling os.getenv everywhere.
.env local values
Docker Compose env_file loads values into container
Docker Compose environment overrides selected values
Settings object validates and normalizes values
Application modules import settings from app.config
Routes do not expose secretsStop or reset the project
Section titled “Stop or reset the project”Stop the running container without deleting the SQLite data.
docker compose downReset the project by removing the Docker volume. This deletes the SQLite database file.
docker compose down -v