Aptiwise
Aptiwise
Aptiwise DocumentationForm Layout Feature - Implementation CompleteForm Layout Feature Implementation Guide
Getting Started
User Guide
Development GuideProduction DeploymentProject StructureTesting Guide
API Development
Authentication & Security
Background Tasks
Caching
Configuration
Docker SetupEnvironment-Specific ConfigurationConfiguration GuideSettings Classes
Database Layer
Rate Limiting
Workflow Types & Patterns
User GuideConfiguration

Environment-Specific Configuration

Environment-Specific Configuration

Learn how to configure your FastAPI application for different environments (development, staging, production) with appropriate security, performance, and monitoring settings.

Environment Types

The boilerplate supports three environment types:

  • local - Development environment with full debugging
  • staging - Pre-production testing environment
  • production - Production environment with security hardening

Set the environment type with:

ENVIRONMENT="local"  # or "staging" or "production"

Development Environment

Local Development Settings

Create src/.env.development:

# ------------- environment -------------
ENVIRONMENT="local"
DEBUG=true

# ------------- app settings -------------
APP_NAME="MyApp (Development)"
APP_VERSION="0.1.0-dev"

# ------------- database -------------
POSTGRES_USER="dev_user"
POSTGRES_PASSWORD="dev_password"
POSTGRES_SERVER="localhost"
POSTGRES_PORT=5432
POSTGRES_DB="myapp_dev"

# ------------- crypt -------------
SECRET_KEY="dev-secret-key-not-for-production-use"
ALGORITHM="HS256"
ACCESS_TOKEN_EXPIRE_MINUTES=60  # Longer for development
REFRESH_TOKEN_EXPIRE_DAYS=30     # Longer for development

# ------------- redis -------------
REDIS_CACHE_HOST="localhost"
REDIS_CACHE_PORT=6379
REDIS_QUEUE_HOST="localhost"
REDIS_QUEUE_PORT=6379
REDIS_RATE_LIMIT_HOST="localhost"
REDIS_RATE_LIMIT_PORT=6379

# ------------- caching -------------
CLIENT_CACHE_MAX_AGE=0  # Disable caching for development

# ------------- rate limiting -------------
DEFAULT_RATE_LIMIT_LIMIT=1000   # Higher limits for development
DEFAULT_RATE_LIMIT_PERIOD=3600

# ------------- admin -------------
ADMIN_NAME="Dev Admin"
ADMIN_EMAIL="admin@localhost"
ADMIN_USERNAME="admin"
ADMIN_PASSWORD="admin123"

# ------------- tier -------------
TIER_NAME="dev_tier"

# ------------- logging -------------
DATABASE_ECHO=true  # Log all SQL queries

Development Features

# Development-specific features
if settings.ENVIRONMENT == "local":
    # Enable detailed error pages
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],  # Allow all origins in development
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    # Enable API documentation
    app.openapi_url = "/openapi.json"
    app.docs_url = "/docs"
    app.redoc_url = "/redoc"

Docker Development Override

docker-compose.override.yml:

version: '3.8'

services:
  web:
    environment:
      - ENVIRONMENT=local
      - DEBUG=true
      - DATABASE_ECHO=true
    volumes:
      - ./src:/code/src:cached
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
    ports:
      - "8000:8000"

  db:
    environment:
      - POSTGRES_DB=myapp_dev
    ports:
      - "5432:5432"

  redis:
    ports:
      - "6379:6379"

  # Development tools
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    depends_on:
      - db

Staging Environment

Staging Settings

Create src/.env.staging:

# ------------- environment -------------
ENVIRONMENT="staging"
DEBUG=false

# ------------- app settings -------------
APP_NAME="MyApp (Staging)"
APP_VERSION="0.1.0-staging"

# ------------- database -------------
POSTGRES_USER="staging_user"
POSTGRES_PASSWORD="complex_staging_password_123!"
POSTGRES_SERVER="staging-db.example.com"
POSTGRES_PORT=5432
POSTGRES_DB="myapp_staging"

# ------------- crypt -------------
SECRET_KEY="staging-secret-key-different-from-production"
ALGORITHM="HS256"
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=7

# ------------- redis -------------
REDIS_CACHE_HOST="staging-redis.example.com"
REDIS_CACHE_PORT=6379
REDIS_QUEUE_HOST="staging-redis.example.com"
REDIS_QUEUE_PORT=6379
REDIS_RATE_LIMIT_HOST="staging-redis.example.com"
REDIS_RATE_LIMIT_PORT=6379

# ------------- caching -------------
CLIENT_CACHE_MAX_AGE=300  # 5 minutes

# ------------- rate limiting -------------
DEFAULT_RATE_LIMIT_LIMIT=100
DEFAULT_RATE_LIMIT_PERIOD=3600

# ------------- admin -------------
ADMIN_NAME="Staging Admin"
ADMIN_EMAIL="admin@staging.example.com"
ADMIN_USERNAME="staging_admin"
ADMIN_PASSWORD="secure_staging_password_456!"

# ------------- tier -------------
TIER_NAME="staging_tier"

# ------------- logging -------------
DATABASE_ECHO=false

Staging Features

# Staging-specific features
if settings.ENVIRONMENT == "staging":
    # Restricted CORS
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["https://staging.example.com"],
        allow_credentials=True,
        allow_methods=["GET", "POST", "PUT", "DELETE"],
        allow_headers=["*"],
    )

    # API docs available to superusers only
    @app.get("/docs", include_in_schema=False)
    async def custom_swagger_ui(current_user: User = Depends(get_current_superuser)):
        return get_swagger_ui_html(openapi_url="/openapi.json")

Docker Staging Configuration

docker-compose.staging.yml:

version: '3.8'

services:
  web:
    environment:
      - ENVIRONMENT=staging
      - DEBUG=false
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M
    restart: always

  db:
    environment:
      - POSTGRES_DB=myapp_staging
    volumes:
      - postgres_staging_data:/var/lib/postgresql/data
    restart: always

  redis:
    restart: always

  worker:
    deploy:
      replicas: 2
    restart: always

volumes:
  postgres_staging_data:

Production Environment

Production Settings

Create src/.env.production:

# ------------- environment -------------
ENVIRONMENT="production"
DEBUG=false

# ------------- app settings -------------
APP_NAME="MyApp"
APP_VERSION="1.0.0"
CONTACT_NAME="Support Team"
CONTACT_EMAIL="support@example.com"

# ------------- database -------------
POSTGRES_USER="prod_user"
POSTGRES_PASSWORD="ultra_secure_production_password_789!"
POSTGRES_SERVER="prod-db.example.com"
POSTGRES_PORT=5433  # Custom port for security
POSTGRES_DB="myapp_production"

# ------------- crypt -------------
SECRET_KEY="ultra-secure-production-key-generated-with-openssl-rand-hex-32"
ALGORITHM="HS256"
ACCESS_TOKEN_EXPIRE_MINUTES=15  # Shorter for security
REFRESH_TOKEN_EXPIRE_DAYS=3     # Shorter for security

# ------------- redis -------------
REDIS_CACHE_HOST="prod-redis.example.com"
REDIS_CACHE_PORT=6380  # Custom port for security
REDIS_QUEUE_HOST="prod-redis.example.com"
REDIS_QUEUE_PORT=6380
REDIS_RATE_LIMIT_HOST="prod-redis.example.com"
REDIS_RATE_LIMIT_PORT=6380

# ------------- caching -------------
CLIENT_CACHE_MAX_AGE=3600  # 1 hour

# ------------- rate limiting -------------
DEFAULT_RATE_LIMIT_LIMIT=100
DEFAULT_RATE_LIMIT_PERIOD=3600

# ------------- admin -------------
ADMIN_NAME="System Administrator"
ADMIN_EMAIL="admin@example.com"
ADMIN_USERNAME="sysadmin"
ADMIN_PASSWORD="extremely_secure_admin_password_with_symbols_#$%!"

# ------------- tier -------------
TIER_NAME="production_tier"

# ------------- logging -------------
DATABASE_ECHO=false

Production Security Features

# Production-specific features
if settings.ENVIRONMENT == "production":
    # Strict CORS
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["https://example.com", "https://www.example.com"],
        allow_credentials=True,
        allow_methods=["GET", "POST", "PUT", "DELETE"],
        allow_headers=["Authorization", "Content-Type"],
    )

    # Disable API documentation
    app.openapi_url = None
    app.docs_url = None
    app.redoc_url = None

    # Add security headers
    @app.middleware("http")
    async def add_security_headers(request: Request, call_next):
        response = await call_next(request)
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
        return response

Docker Production Configuration

docker-compose.prod.yml:

version: '3.8'

services:
  web:
    environment:
      - ENVIRONMENT=production
      - DEBUG=false
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 2G
          cpus: '1'
        reservations:
          memory: 1G
          cpus: '0.5'
    restart: always
    ports: []  # No direct exposure

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/ssl:/etc/nginx/ssl
      - ./nginx/htpasswd:/etc/nginx/htpasswd
    depends_on:
      - web
    restart: always

  db:
    environment:
      - POSTGRES_DB=myapp_production
    volumes:
      - postgres_prod_data:/var/lib/postgresql/data
    ports: []  # No external access
    deploy:
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G
    restart: always

  redis:
    volumes:
      - redis_prod_data:/data
    ports: []  # No external access
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M
    restart: always

  worker:
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M
    restart: always

volumes:
  postgres_prod_data:
  redis_prod_data:

Environment Detection

Runtime Environment Checks

# src/app/core/config.py
class Settings(BaseSettings):
    @computed_field
    @property
    def IS_DEVELOPMENT(self) -> bool:
        return self.ENVIRONMENT == "local"

    @computed_field
    @property
    def IS_PRODUCTION(self) -> bool:
        return self.ENVIRONMENT == "production"

    @computed_field
    @property
    def IS_STAGING(self) -> bool:
        return self.ENVIRONMENT == "staging"


# Use in application
if settings.IS_DEVELOPMENT:
    # Development-only code
    pass

if settings.IS_PRODUCTION:
    # Production-only code
    pass

Environment-Specific Validation

@model_validator(mode="after")
def validate_environment_config(self) -> "Settings":
    if self.ENVIRONMENT == "production":
        # Production validation
        if self.DEBUG:
            raise ValueError("DEBUG must be False in production")
        if len(self.SECRET_KEY) < 32:
            raise ValueError("SECRET_KEY must be at least 32 characters in production")
        if "dev" in self.SECRET_KEY.lower():
            raise ValueError("Production SECRET_KEY cannot contain 'dev'")

    if self.ENVIRONMENT == "local":
        # Development warnings
        if not self.DEBUG:
            logger.warning("DEBUG is False in development environment")

    return self

Configuration Management

Environment File Templates

Create template files for each environment:

# Create environment templates
cp src/.env.example src/.env.development
cp src/.env.example src/.env.staging
cp src/.env.example src/.env.production

# Use environment-specific files
ln -sf .env.development src/.env  # For development
ln -sf .env.staging src/.env      # For staging
ln -sf .env.production src/.env   # For production

Configuration Validation

# src/scripts/validate_config.py
import asyncio
from src.app.core.config import settings
from src.app.core.db.database import async_get_db


async def validate_configuration():
    """Validate configuration for current environment."""
    print(f"Validating configuration for {settings.ENVIRONMENT} environment...")

    # Basic settings validation
    assert settings.APP_NAME, "APP_NAME is required"
    assert settings.SECRET_KEY, "SECRET_KEY is required"
    assert len(settings.SECRET_KEY) >= 32, "SECRET_KEY must be at least 32 characters"

    # Environment-specific validation
    if settings.ENVIRONMENT == "production":
        assert not settings.DEBUG, "DEBUG must be False in production"
        assert "dev" not in settings.SECRET_KEY.lower(), "Production SECRET_KEY invalid"
        assert settings.POSTGRES_PORT != 5432, "Use custom PostgreSQL port in production"

    # Test database connection
    try:
        db = await anext(async_get_db())
        print("✓ Database connection successful")
        await db.close()
    except Exception as e:
        print(f"✗ Database connection failed: {e}")
        return False

    print("✓ Configuration validation passed")
    return True


if __name__ == "__main__":
    asyncio.run(validate_configuration())

Environment Switching

#!/bin/bash
# scripts/switch_env.sh

ENV=$1

if [ -z "$ENV" ]; then
    echo "Usage: $0 <development|staging|production>"
    exit 1
fi

case $ENV in
    development)
        ln -sf .env.development src/.env
        echo "Switched to development environment"
        ;;
    staging)
        ln -sf .env.staging src/.env
        echo "Switched to staging environment"
        ;;
    production)
        ln -sf .env.production src/.env
        echo "Switched to production environment"
        echo "WARNING: Make sure to review all settings before deployment!"
        ;;
    *)
        echo "Invalid environment: $ENV"
        echo "Valid options: development, staging, production"
        exit 1
        ;;
esac

# Validate configuration
python -c "from src.app.core.config import settings; print(f'Current environment: {settings.ENVIRONMENT}')"

Security Best Practices

Environment-Specific Security

# Different security levels per environment
SECURITY_CONFIGS = {
    "local": {
        "token_expire_minutes": 60,
        "enable_cors_origins": ["*"],
        "enable_docs": True,
        "log_level": "DEBUG",
    },
    "staging": {
        "token_expire_minutes": 30,
        "enable_cors_origins": ["https://staging.example.com"],
        "enable_docs": True,  # For testing
        "log_level": "INFO",
    },
    "production": {
        "token_expire_minutes": 15,
        "enable_cors_origins": ["https://example.com"],
        "enable_docs": False,
        "log_level": "WARNING",
    },
}

config = SECURITY_CONFIGS[settings.ENVIRONMENT]

Secrets Management

# Use secrets management in production
# Instead of plain text environment variables
POSTGRES_PASSWORD_FILE="/run/secrets/postgres_password"
SECRET_KEY_FILE="/run/secrets/jwt_secret"

# Docker secrets
services:
  web:
    secrets:
      - postgres_password
      - jwt_secret
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
      - SECRET_KEY_FILE=/run/secrets/jwt_secret

secrets:
  postgres_password:
    external: true
  jwt_secret:
    external: true

Monitoring and Logging

Environment-Specific Logging

LOGGING_CONFIG = {
    "local": {
        "level": "DEBUG",
        "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
        "handlers": ["console"],
    },
    "staging": {
        "level": "INFO",
        "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
        "handlers": ["console", "file"],
    },
    "production": {
        "level": "WARNING",
        "format": "%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s",
        "handlers": ["file", "syslog"],
    },
}

Health Checks by Environment

@app.get("/health")
async def health_check():
    health_info = {
        "status": "healthy",
        "environment": settings.ENVIRONMENT,
        "version": settings.APP_VERSION,
    }

    # Add detailed info in non-production
    if not settings.IS_PRODUCTION:
        health_info.update(
            {
                "database": await check_database_health(),
                "redis": await check_redis_health(),
                "worker_queue": await check_worker_health(),
            }
        )

    return health_info

Best Practices

Security

  • Use different secret keys for each environment
  • Disable debug mode in staging and production
  • Use custom ports in production
  • Implement proper CORS policies
  • Remove API documentation in production

Performance

  • Configure appropriate resource limits per environment
  • Use caching in staging and production
  • Set shorter token expiration in production
  • Use connection pooling in production

Configuration

  • Keep environment files in version control (except production)
  • Use validation to prevent misconfiguration
  • Document all environment-specific settings
  • Test configuration changes in staging first

Monitoring

  • Use appropriate log levels per environment
  • Monitor different metrics in each environment
  • Set up alerts for production only
  • Use health checks for all environments

Environment-specific configuration ensures your application runs securely and efficiently in each deployment stage. Start with development settings and progressively harden for production!

Docker Setup

Previous Page

Configuration Guide

Next Page

On this page

Environment-Specific ConfigurationEnvironment TypesDevelopment EnvironmentLocal Development SettingsDevelopment FeaturesDocker Development OverrideStaging EnvironmentStaging SettingsStaging FeaturesDocker Staging ConfigurationProduction EnvironmentProduction SettingsProduction Security FeaturesDocker Production ConfigurationEnvironment DetectionRuntime Environment ChecksEnvironment-Specific ValidationConfiguration ManagementEnvironment File TemplatesConfiguration ValidationEnvironment SwitchingSecurity Best PracticesEnvironment-Specific SecuritySecrets ManagementMonitoring and LoggingEnvironment-Specific LoggingHealth Checks by EnvironmentBest PracticesSecurityPerformanceConfigurationMonitoring