651 lines
15 KiB
Markdown
651 lines
15 KiB
Markdown
# Configuration Guide
|
|
|
|
This guide covers all configuration options available in the FastAPI Boilerplate, including environment variables, settings classes, and advanced deployment configurations.
|
|
|
|
## Configuration Overview
|
|
|
|
The boilerplate uses a layered configuration approach:
|
|
|
|
- **Environment Variables** (`.env` file) - Primary configuration method
|
|
- **Settings Classes** (`src/app/core/config.py`) - Python-based configuration
|
|
- **Docker Configuration** (`docker-compose.yml`) - Container orchestration
|
|
- **Database Configuration** (`alembic.ini`) - Database migrations
|
|
|
|
## Environment Variables Reference
|
|
|
|
All configuration is managed through environment variables defined in the `.env` file located in the `src/` directory.
|
|
|
|
### Application Settings
|
|
|
|
Basic application metadata displayed in API documentation:
|
|
|
|
```env
|
|
# ------------- app settings -------------
|
|
APP_NAME="Your App Name"
|
|
APP_DESCRIPTION="Your app description here"
|
|
APP_VERSION="0.1.0"
|
|
CONTACT_NAME="Your Name"
|
|
CONTACT_EMAIL="your.email@example.com"
|
|
LICENSE_NAME="MIT"
|
|
```
|
|
|
|
**Variables Explained:**
|
|
|
|
- `APP_NAME`: Displayed in API documentation and responses
|
|
- `APP_DESCRIPTION`: Shown in OpenAPI documentation
|
|
- `APP_VERSION`: API version for documentation and headers
|
|
- `CONTACT_NAME`: Contact information for API documentation
|
|
- `CONTACT_EMAIL`: Support email for API users
|
|
- `LICENSE_NAME`: License type for the API
|
|
|
|
### Database Configuration
|
|
|
|
PostgreSQL database connection settings:
|
|
|
|
```env
|
|
# ------------- database -------------
|
|
POSTGRES_USER="your_postgres_user"
|
|
POSTGRES_PASSWORD="your_secure_password"
|
|
POSTGRES_SERVER="localhost"
|
|
POSTGRES_PORT=5432
|
|
POSTGRES_DB="your_database_name"
|
|
```
|
|
|
|
**Variables Explained:**
|
|
|
|
- `POSTGRES_USER`: Database user with appropriate permissions
|
|
- `POSTGRES_PASSWORD`: Strong password for database access
|
|
- `POSTGRES_SERVER`: Hostname or IP of PostgreSQL server
|
|
- `POSTGRES_PORT`: PostgreSQL port (default: 5432)
|
|
- `POSTGRES_DB`: Name of the database to connect to
|
|
|
|
**Environment-Specific Values:**
|
|
|
|
```env
|
|
# Local development
|
|
POSTGRES_SERVER="localhost"
|
|
|
|
# Docker Compose
|
|
POSTGRES_SERVER="db"
|
|
|
|
# Production
|
|
POSTGRES_SERVER="your-prod-db-host.com"
|
|
```
|
|
|
|
### Security & Authentication
|
|
|
|
JWT and password security configuration:
|
|
|
|
```env
|
|
# ------------- crypt -------------
|
|
SECRET_KEY="your-super-secret-key-here"
|
|
ALGORITHM="HS256"
|
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
|
REFRESH_TOKEN_EXPIRE_DAYS=7
|
|
```
|
|
|
|
**Variables Explained:**
|
|
|
|
- `SECRET_KEY`: Used for JWT token signing (generate with `openssl rand -hex 32`)
|
|
- `ALGORITHM`: JWT signing algorithm (HS256 recommended)
|
|
- `ACCESS_TOKEN_EXPIRE_MINUTES`: How long access tokens remain valid
|
|
- `REFRESH_TOKEN_EXPIRE_DAYS`: How long refresh tokens remain valid
|
|
|
|
!!! danger "Security Warning"
|
|
Never use default values in production. Generate a strong secret key:
|
|
```bash
|
|
openssl rand -hex 32
|
|
```
|
|
|
|
### Redis Configuration
|
|
|
|
Redis is used for caching, job queues, and rate limiting:
|
|
|
|
```env
|
|
# ------------- redis cache -------------
|
|
REDIS_CACHE_HOST="localhost" # Use "redis" for Docker Compose
|
|
REDIS_CACHE_PORT=6379
|
|
|
|
# ------------- redis queue -------------
|
|
REDIS_QUEUE_HOST="localhost" # Use "redis" for Docker Compose
|
|
REDIS_QUEUE_PORT=6379
|
|
|
|
# ------------- redis rate limit -------------
|
|
REDIS_RATE_LIMIT_HOST="localhost" # Use "redis" for Docker Compose
|
|
REDIS_RATE_LIMIT_PORT=6379
|
|
```
|
|
|
|
**Best Practices:**
|
|
|
|
- **Development**: Use the same Redis instance for all services
|
|
- **Production**: Use separate Redis instances for better isolation
|
|
|
|
```env
|
|
# Production example with separate instances
|
|
REDIS_CACHE_HOST="cache.redis.example.com"
|
|
REDIS_QUEUE_HOST="queue.redis.example.com"
|
|
REDIS_RATE_LIMIT_HOST="ratelimit.redis.example.com"
|
|
```
|
|
|
|
### Caching Settings
|
|
|
|
Client-side and server-side caching configuration:
|
|
|
|
```env
|
|
# ------------- redis client-side cache -------------
|
|
CLIENT_CACHE_MAX_AGE=30 # seconds
|
|
```
|
|
|
|
**Variables Explained:**
|
|
|
|
- `CLIENT_CACHE_MAX_AGE`: How long browsers should cache responses
|
|
|
|
### Rate Limiting
|
|
|
|
Default rate limiting configuration:
|
|
|
|
```env
|
|
# ------------- default rate limit settings -------------
|
|
DEFAULT_RATE_LIMIT_LIMIT=10 # requests per period
|
|
DEFAULT_RATE_LIMIT_PERIOD=3600 # period in seconds (1 hour)
|
|
```
|
|
|
|
**Variables Explained:**
|
|
|
|
- `DEFAULT_RATE_LIMIT_LIMIT`: Number of requests allowed per period
|
|
- `DEFAULT_RATE_LIMIT_PERIOD`: Time window in seconds
|
|
|
|
### Admin User
|
|
|
|
First superuser account configuration:
|
|
|
|
```env
|
|
# ------------- admin -------------
|
|
ADMIN_NAME="Admin User"
|
|
ADMIN_EMAIL="admin@example.com"
|
|
ADMIN_USERNAME="admin"
|
|
ADMIN_PASSWORD="secure_admin_password"
|
|
```
|
|
|
|
**Variables Explained:**
|
|
|
|
- `ADMIN_NAME`: Display name for the admin user
|
|
- `ADMIN_EMAIL`: Email address for the admin account
|
|
- `ADMIN_USERNAME`: Username for admin login
|
|
- `ADMIN_PASSWORD`: Initial password (change after first login)
|
|
|
|
### User Tiers
|
|
|
|
Initial tier configuration:
|
|
|
|
```env
|
|
# ------------- first tier -------------
|
|
TIER_NAME="free"
|
|
```
|
|
|
|
**Variables Explained:**
|
|
|
|
- `TIER_NAME`: Name of the default user tier
|
|
|
|
### Environment Type
|
|
|
|
Controls API documentation visibility and behavior:
|
|
|
|
```env
|
|
# ------------- environment -------------
|
|
ENVIRONMENT="local" # local, staging, or production
|
|
```
|
|
|
|
**Environment Types:**
|
|
|
|
- **local**: Full API docs available publicly at `/docs`
|
|
- **staging**: API docs available to superusers only
|
|
- **production**: API docs completely disabled
|
|
|
|
## Docker Compose Configuration
|
|
|
|
### Basic Setup
|
|
|
|
Docker Compose automatically loads the `.env` file:
|
|
|
|
```yaml
|
|
# In docker-compose.yml
|
|
services:
|
|
web:
|
|
env_file:
|
|
- ./src/.env
|
|
```
|
|
|
|
### Development Overrides
|
|
|
|
Create `docker-compose.override.yml` for local customizations:
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
services:
|
|
web:
|
|
ports:
|
|
- "8001:8000" # Use different port
|
|
environment:
|
|
- DEBUG=true
|
|
volumes:
|
|
- ./custom-logs:/code/logs
|
|
```
|
|
|
|
### Service Configuration
|
|
|
|
Understanding each Docker service:
|
|
|
|
```yaml
|
|
services:
|
|
web: # FastAPI application
|
|
db: # PostgreSQL database
|
|
redis: # Redis for caching/queues
|
|
worker: # ARQ background task worker
|
|
nginx: # Reverse proxy (optional)
|
|
```
|
|
|
|
## Python Settings Classes
|
|
|
|
Advanced configuration is handled in `src/app/core/config.py`:
|
|
|
|
### Settings Composition
|
|
|
|
The main `Settings` class inherits from multiple setting groups:
|
|
|
|
```python
|
|
class Settings(
|
|
AppSettings,
|
|
PostgresSettings,
|
|
CryptSettings,
|
|
FirstUserSettings,
|
|
RedisCacheSettings,
|
|
ClientSideCacheSettings,
|
|
RedisQueueSettings,
|
|
RedisRateLimiterSettings,
|
|
DefaultRateLimitSettings,
|
|
EnvironmentSettings,
|
|
):
|
|
pass
|
|
```
|
|
|
|
### Adding Custom Settings
|
|
|
|
Create your own settings group:
|
|
|
|
```python
|
|
class CustomSettings(BaseSettings):
|
|
CUSTOM_API_KEY: str = ""
|
|
CUSTOM_TIMEOUT: int = 30
|
|
ENABLE_FEATURE_X: bool = False
|
|
|
|
# Add to main Settings class
|
|
class Settings(
|
|
AppSettings,
|
|
# ... other settings ...
|
|
CustomSettings,
|
|
):
|
|
pass
|
|
```
|
|
|
|
### Opting Out of Services
|
|
|
|
Remove unused services by excluding their settings:
|
|
|
|
```python
|
|
# Minimal setup without Redis services
|
|
class Settings(
|
|
AppSettings,
|
|
PostgresSettings,
|
|
CryptSettings,
|
|
FirstUserSettings,
|
|
# Removed: RedisCacheSettings
|
|
# Removed: RedisQueueSettings
|
|
# Removed: RedisRateLimiterSettings
|
|
EnvironmentSettings,
|
|
):
|
|
pass
|
|
```
|
|
|
|
## Database Configuration
|
|
|
|
### Alembic Configuration
|
|
|
|
Database migrations are configured in `src/alembic.ini`:
|
|
|
|
```ini
|
|
[alembic]
|
|
script_location = migrations
|
|
sqlalchemy.url = postgresql://%(POSTGRES_USER)s:%(POSTGRES_PASSWORD)s@%(POSTGRES_SERVER)s:%(POSTGRES_PORT)s/%(POSTGRES_DB)s
|
|
```
|
|
|
|
### Connection Pooling
|
|
|
|
SQLAlchemy connection pool settings in `src/app/core/db/database.py`:
|
|
|
|
```python
|
|
engine = create_async_engine(
|
|
DATABASE_URL,
|
|
pool_size=20, # Number of connections to maintain
|
|
max_overflow=30, # Additional connections allowed
|
|
pool_timeout=30, # Seconds to wait for connection
|
|
pool_recycle=1800, # Seconds before connection refresh
|
|
)
|
|
```
|
|
|
|
### Database Best Practices
|
|
|
|
**Connection Pool Sizing:**
|
|
- Start with `pool_size=20`, `max_overflow=30`
|
|
- Monitor connection usage and adjust based on load
|
|
- Use connection pooling monitoring tools
|
|
|
|
**Migration Strategy:**
|
|
- Always backup database before running migrations
|
|
- Test migrations on staging environment first
|
|
- Use `alembic revision --autogenerate` for model changes
|
|
|
|
## Security Configuration
|
|
|
|
### JWT Token Configuration
|
|
|
|
Customize JWT behavior in `src/app/core/security.py`:
|
|
|
|
```python
|
|
def create_access_token(data: dict, expires_delta: timedelta = None):
|
|
to_encode = data.copy()
|
|
if expires_delta:
|
|
expire = datetime.utcnow() + expires_delta
|
|
else:
|
|
expire = datetime.utcnow() + timedelta(
|
|
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
|
)
|
|
```
|
|
|
|
### CORS Configuration
|
|
|
|
Configure Cross-Origin Resource Sharing in `src/app/main.py`:
|
|
|
|
```python
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["http://localhost:3000"], # Specify allowed origins
|
|
allow_credentials=True,
|
|
allow_methods=["GET", "POST"], # Specify allowed methods
|
|
allow_headers=["*"],
|
|
)
|
|
```
|
|
|
|
**Production CORS Settings:**
|
|
|
|
```python
|
|
# Never use wildcard (*) in production
|
|
allow_origins=[
|
|
"https://yourapp.com",
|
|
"https://www.yourapp.com"
|
|
],
|
|
```
|
|
|
|
### Security Headers
|
|
|
|
Add security headers middleware:
|
|
|
|
```python
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
|
|
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|
async def dispatch(self, request, call_next):
|
|
response = await call_next(request)
|
|
response.headers["X-Frame-Options"] = "DENY"
|
|
response.headers["X-Content-Type-Options"] = "nosniff"
|
|
response.headers["X-XSS-Protection"] = "1; mode=block"
|
|
return response
|
|
```
|
|
|
|
## Logging Configuration
|
|
|
|
### Basic Logging Setup
|
|
|
|
Configure logging in `src/app/core/logger.py`:
|
|
|
|
```python
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
|
|
# Set log level
|
|
LOGGING_LEVEL = logging.INFO
|
|
|
|
# Configure file rotation
|
|
file_handler = RotatingFileHandler(
|
|
'logs/app.log',
|
|
maxBytes=10485760, # 10MB
|
|
backupCount=5 # Keep 5 backup files
|
|
)
|
|
```
|
|
|
|
### Structured Logging
|
|
|
|
Use structured logging for better observability:
|
|
|
|
```python
|
|
import structlog
|
|
|
|
structlog.configure(
|
|
processors=[
|
|
structlog.stdlib.filter_by_level,
|
|
structlog.stdlib.add_logger_name,
|
|
structlog.stdlib.add_log_level,
|
|
structlog.processors.JSONRenderer()
|
|
],
|
|
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
)
|
|
```
|
|
|
|
### Log Levels by Environment
|
|
|
|
```python
|
|
# Environment-specific log levels
|
|
LOG_LEVELS = {
|
|
"local": logging.DEBUG,
|
|
"staging": logging.INFO,
|
|
"production": logging.WARNING
|
|
}
|
|
|
|
LOGGING_LEVEL = LOG_LEVELS.get(settings.ENVIRONMENT, logging.INFO)
|
|
```
|
|
|
|
## Environment-Specific Configurations
|
|
|
|
### Development (.env.development)
|
|
|
|
```env
|
|
ENVIRONMENT="local"
|
|
POSTGRES_SERVER="localhost"
|
|
REDIS_CACHE_HOST="localhost"
|
|
SECRET_KEY="dev-secret-key-not-for-production"
|
|
ACCESS_TOKEN_EXPIRE_MINUTES=60 # Longer for development
|
|
DEBUG=true
|
|
```
|
|
|
|
### Staging (.env.staging)
|
|
|
|
```env
|
|
ENVIRONMENT="staging"
|
|
POSTGRES_SERVER="staging-db.example.com"
|
|
REDIS_CACHE_HOST="staging-redis.example.com"
|
|
SECRET_KEY="staging-secret-key-different-from-prod"
|
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
|
DEBUG=false
|
|
```
|
|
|
|
### Production (.env.production)
|
|
|
|
```env
|
|
ENVIRONMENT="production"
|
|
POSTGRES_SERVER="prod-db.example.com"
|
|
REDIS_CACHE_HOST="prod-redis.example.com"
|
|
SECRET_KEY="ultra-secure-production-key-generated-with-openssl"
|
|
ACCESS_TOKEN_EXPIRE_MINUTES=15
|
|
DEBUG=false
|
|
REDIS_CACHE_PORT=6380 # Custom port for security
|
|
POSTGRES_PORT=5433 # Custom port for security
|
|
```
|
|
|
|
## Advanced Configuration
|
|
|
|
### Custom Middleware
|
|
|
|
Add custom middleware in `src/app/core/setup.py`:
|
|
|
|
```python
|
|
def create_application(router, settings, **kwargs):
|
|
app = FastAPI(...)
|
|
|
|
# Add custom middleware
|
|
app.add_middleware(CustomMiddleware, setting=value)
|
|
app.add_middleware(TimingMiddleware)
|
|
app.add_middleware(RequestIDMiddleware)
|
|
|
|
return app
|
|
```
|
|
|
|
### Feature Toggles
|
|
|
|
Implement feature flags:
|
|
|
|
```python
|
|
class FeatureSettings(BaseSettings):
|
|
ENABLE_ADVANCED_CACHING: bool = False
|
|
ENABLE_ANALYTICS: bool = True
|
|
ENABLE_EXPERIMENTAL_FEATURES: bool = False
|
|
ENABLE_API_VERSIONING: bool = True
|
|
|
|
# Use in endpoints
|
|
if settings.ENABLE_ADVANCED_CACHING:
|
|
# Advanced caching logic
|
|
pass
|
|
```
|
|
|
|
### Health Checks
|
|
|
|
Configure health check endpoints:
|
|
|
|
```python
|
|
@app.get("/health")
|
|
async def health_check():
|
|
return {
|
|
"status": "healthy",
|
|
"database": await check_database_health(),
|
|
"redis": await check_redis_health(),
|
|
"version": settings.APP_VERSION
|
|
}
|
|
```
|
|
|
|
## Configuration Validation
|
|
|
|
### Environment Validation
|
|
|
|
Add validation to prevent misconfiguration:
|
|
|
|
```python
|
|
def validate_settings():
|
|
if not settings.SECRET_KEY:
|
|
raise ValueError("SECRET_KEY must be set")
|
|
|
|
if settings.ENVIRONMENT == "production":
|
|
if settings.SECRET_KEY == "dev-secret-key":
|
|
raise ValueError("Production must use secure SECRET_KEY")
|
|
|
|
if settings.DEBUG:
|
|
raise ValueError("DEBUG must be False in production")
|
|
```
|
|
|
|
### Runtime Checks
|
|
|
|
Add validation to application startup:
|
|
|
|
```python
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
validate_settings()
|
|
await check_database_connection()
|
|
await check_redis_connection()
|
|
logger.info(f"Application started in {settings.ENVIRONMENT} mode")
|
|
```
|
|
|
|
## Configuration Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
**Environment Variables Not Loading:**
|
|
```bash
|
|
# Check file location and permissions
|
|
ls -la src/.env
|
|
|
|
# Check file format (no spaces around =)
|
|
cat src/.env | grep "=" | head -5
|
|
|
|
# Verify environment loading in Python
|
|
python -c "from src.app.core.config import settings; print(settings.APP_NAME)"
|
|
```
|
|
|
|
**Database Connection Failed:**
|
|
```bash
|
|
# Test connection manually
|
|
psql -h localhost -U postgres -d myapp
|
|
|
|
# Check if PostgreSQL is running
|
|
systemctl status postgresql
|
|
# or on macOS
|
|
brew services list | grep postgresql
|
|
```
|
|
|
|
**Redis Connection Failed:**
|
|
```bash
|
|
# Test Redis connection
|
|
redis-cli -h localhost -p 6379 ping
|
|
|
|
# Check Redis status
|
|
systemctl status redis
|
|
# or on macOS
|
|
brew services list | grep redis
|
|
```
|
|
|
|
### Configuration Testing
|
|
|
|
Test your configuration with a simple script:
|
|
|
|
```python
|
|
# test_config.py
|
|
import asyncio
|
|
from src.app.core.config import settings
|
|
from src.app.core.db.database import async_get_db
|
|
|
|
async def test_config():
|
|
print(f"App: {settings.APP_NAME}")
|
|
print(f"Environment: {settings.ENVIRONMENT}")
|
|
|
|
# Test database
|
|
try:
|
|
db = await anext(async_get_db())
|
|
print("✓ Database connection successful")
|
|
await db.close()
|
|
except Exception as e:
|
|
print(f"✗ Database connection failed: {e}")
|
|
|
|
# Test Redis (if enabled)
|
|
try:
|
|
from src.app.core.utils.cache import redis_client
|
|
await redis_client.ping()
|
|
print("✓ Redis connection successful")
|
|
except Exception as e:
|
|
print(f"✗ Redis connection failed: {e}")
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(test_config())
|
|
```
|
|
|
|
Run with:
|
|
```bash
|
|
uv run python test_config.py
|
|
``` |