initial commit
This commit is contained in:
651
docs/user-guide/configuration/environment-variables.md
Normal file
651
docs/user-guide/configuration/environment-variables.md
Normal file
@ -0,0 +1,651 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user