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
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-environment
cd 07-settings-environment
mkdir app
touch app/__init__.pyCreate 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 Example
ENVIRONMENT=development
DEBUG=true
DATABASE_URL=sqlite:////data/app.db
SECRET_KEY=change-this-dev-secret
ACCESS_TOKEN_EXPIRE_MINUTES=30
CORS_ORIGINS=http://localhost:3000,http://localhost:8000Create 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 Example
ENVIRONMENT=development
DEBUG=true
DATABASE_URL=sqlite:////data/app.db
SECRET_KEY=local-dev-secret-value
ACCESS_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
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_validator
from 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_cache
def 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
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_engine
from 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
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 FastAPI
from 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
Create the dependency file at requirements.txt. pydantic-settings provides the BaseSettings class for environment based configuration.
requirements.txtfastapi
uvicorn[standard]
sqlalchemy
pydantic-settingsCreate 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=1
ENV PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --upgrade pip
RUN 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
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
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
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
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
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=short
ACCESS_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
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
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
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