# Environment Detection System **Version:** 1.0 **Last Updated:** November 2025 **Audience:** Development Team --- ## Table of Contents 1. [Overview](#overview) 2. [How It Works](#how-it-works) 3. [Using Environment Detection](#using-environment-detection) 4. [Configuration](#configuration) 5. [Development Guidelines](#development-guidelines) 6. [Deployment Guide](#deployment-guide) 7. [Testing](#testing) 8. [Best Practices](#best-practices) --- ## Overview The Orion platform uses **automatic environment detection** to determine the runtime environment (development, staging, or production) and adjust security settings accordingly. ### Why Auto-Detection? Instead of manually configuring environment settings in code, the system automatically detects the environment based on: - Environment variables - Runtime indicators (hostname, debug mode, etc.) - Safe defaults for local development This approach: - ✅ Works out of the box in development (no configuration needed) - ✅ Reduces configuration errors - ✅ Follows "convention over configuration" principle - ✅ Simplifies deployment process ### Key Use Cases The environment detection system is primarily used to determine: 1. **Cookie Security** - Should cookies require HTTPS? (`secure` flag) 2. **Debug Mode** - Enable detailed error messages in development 3. **CORS Settings** - Stricter in production, relaxed in development 4. **Logging Level** - Verbose in development, structured in production --- ## How It Works ### Detection Logic The system determines the environment using a priority-based approach: ``` 1. Check ENV environment variable ↓ (if not set) 2. Check ENVIRONMENT environment variable ↓ (if not set) 3. Auto-detect from system indicators ↓ (if no indicators) 4. Default to "development" (safe for local work) ``` ### Detection Details #### Priority 1: ENV Variable ```bash ENV=production ENV=staging ENV=development # or "dev" or "local" ``` #### Priority 2: ENVIRONMENT Variable ```bash ENVIRONMENT=production ENVIRONMENT=staging ENVIRONMENT=development ``` #### Priority 3: Auto-Detection The system checks for indicators: **Development Indicators:** - `DEBUG=true` environment variable - Hostname contains: "local", "dev", "laptop", "desktop" - Running on: localhost, 127.0.0.1 **Staging Indicators:** - Hostname contains: "staging", "stage" **Production Indicators:** - None of the above (explicit production setting recommended) #### Priority 4: Safe Default If no environment is detected: **defaults to development** - Safe for local development - Cookies work with HTTP - Detailed error messages enabled --- ## Using Environment Detection ### Module Location ```python app/core/environment.py ``` ### Available Functions ```python from app.core.environment import ( get_environment, # Returns: "development" | "staging" | "production" is_development, # Returns: bool is_staging, # Returns: bool is_production, # Returns: bool should_use_secure_cookies, # Returns: bool get_cached_environment, # Returns: cached environment (performance) ) ``` ### Common Usage Patterns #### 1. Cookie Security Settings ```python from fastapi import Response from app.core.environment import should_use_secure_cookies @router.post("/api/v1/admin/auth/login") def login(response: Response): # Set authentication cookie response.set_cookie( key="admin_token", value=token, httponly=True, secure=should_use_secure_cookies(), # Auto-detects environment samesite="lax", path="/admin" ) return {"message": "Logged in"} ``` **Behavior:** - **Development** → `secure=False` (works with HTTP) - **Production** → `secure=True` (requires HTTPS) #### 2. Conditional Features ```python from app.core.environment import is_development, is_production @router.get("/api/v1/debug/info") def debug_info(): if not is_development(): raise HTTPException(status_code=404, detail="Not found") # Only accessible in development return { "database": get_db_info(), "cache": get_cache_stats(), "config": get_config_dump() } ``` #### 3. Error Messages ```python from app.core.environment import is_development @app.exception_handler(Exception) async def exception_handler(request: Request, exc: Exception): if is_development(): # Detailed error in development return JSONResponse( status_code=500, content={ "error": str(exc), "traceback": traceback.format_exc(), "request": str(request.url) } ) else: # Generic error in production return JSONResponse( status_code=500, content={"error": "Internal server error"} ) ``` #### 4. CORS Configuration ```python from fastapi.middleware.cors import CORSMiddleware from app.core.environment import is_development if is_development(): # Relaxed CORS for development app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) else: # Strict CORS for production app.add_middleware( CORSMiddleware, allow_origins=["https://yourdomain.com"], allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE"], allow_headers=["Content-Type", "Authorization"], ) ``` #### 5. Logging Configuration ```python import logging from app.core.environment import get_environment def configure_logging(): env = get_environment() if env == "development": # Verbose logging for development logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) elif env == "staging": # Moderate logging for staging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) else: # production # Structured logging for production logging.basicConfig( level=logging.WARNING, format='{"timestamp":"%(asctime)s","level":"%(levelname)s","message":"%(message)s"}' ) ``` #### 6. Performance Optimization ```python from app.core.environment import get_cached_environment def process_request(request: Request): # Use cached version for frequent calls env = get_cached_environment() # Cached - faster # Instead of: # env = get_environment() # Re-detects every time if env == "production": # Production-specific optimization use_cache = True else: use_cache = False ``` --- ## Configuration ### Development (Default) **No configuration needed!** The system auto-detects development mode. **Indicators:** - Running on localhost or 127.0.0.1 - No environment variables set - DEBUG mode enabled **Settings:** - `should_use_secure_cookies()` → `False` - Cookies work with HTTP (http://localhost:8000) - Detailed error messages ### Staging Environment **Set environment variable:** ```bash # Option 1: ENV export ENV=staging # Option 2: ENVIRONMENT export ENVIRONMENT=staging # Start application uvicorn main:app --host 0.0.0.0 --port 8000 ``` **Or in Docker:** ```dockerfile ENV ENV=staging ``` **Settings:** - `should_use_secure_cookies()` → `True` - Requires HTTPS - Moderate logging ### Production Environment **Set environment variable:** ```bash # Option 1: ENV export ENV=production # Option 2: ENVIRONMENT export ENVIRONMENT=production # Start application uvicorn main:app --host 0.0.0.0 --port 8000 ``` **Or in systemd service:** ```ini [Service] Environment="ENV=production" ExecStart=/usr/bin/uvicorn main:app --host 0.0.0.0 --port 8000 ``` **Or in Docker Compose:** ```yaml services: web: image: orion:latest environment: - ENV=production ports: - "8000:8000" ``` **Settings:** - `should_use_secure_cookies()` → `True` - Requires HTTPS - Minimal logging (warnings/errors only) - Generic error messages --- ## Development Guidelines ### When Writing New Code #### Use Environment Functions **DO:** ```python from app.core.environment import should_use_secure_cookies, is_development # Use the utility functions if is_development(): enable_debug_toolbar() response.set_cookie(secure=should_use_secure_cookies()) ``` **DON'T:** ```python # Don't manually check environment strings if os.getenv("ENV") == "production": # ❌ Bad ... # Don't hardcode environment logic response.set_cookie(secure=True) # ❌ Won't work in development ``` #### Keep Environment Logic Centralized If you need custom environment-based behavior: **Option 1: Add to environment.py** ```python # app/core/environment.py def should_enable_caching() -> bool: """Determine if caching should be enabled.""" return not is_development() def get_max_upload_size() -> int: """Get maximum upload size based on environment.""" env = get_environment() if env == "development": return 10 * 1024 * 1024 # 10MB in dev elif env == "staging": return 50 * 1024 * 1024 # 50MB in staging else: return 100 * 1024 * 1024 # 100MB in prod ``` **Option 2: Use in your module** ```python from app.core.environment import is_production def get_cache_ttl() -> int: """Get cache TTL based on environment.""" return 3600 if is_production() else 60 # 1 hour in prod, 1 min in dev ``` ### Testing Environment Detection #### Unit Tests ```python import os import pytest from app.core.environment import get_environment, should_use_secure_cookies def test_environment_detection_with_env_var(): """Test environment detection with ENV variable.""" os.environ["ENV"] = "production" # Clear cache first import app.core.environment as env_module env_module._cached_environment = None assert get_environment() == "production" assert should_use_secure_cookies() == True # Cleanup del os.environ["ENV"] def test_environment_defaults_to_development(): """Test that environment defaults to development.""" # Ensure no env vars set os.environ.pop("ENV", None) os.environ.pop("ENVIRONMENT", None) # Clear cache import app.core.environment as env_module env_module._cached_environment = None assert get_environment() == "development" assert should_use_secure_cookies() == False ``` #### Integration Tests ```python def test_login_sets_secure_cookie_in_production(test_client, monkeypatch): """Test that login sets secure cookie in production.""" # Mock production environment monkeypatch.setenv("ENV", "production") # Clear cache import app.core.environment as env_module env_module._cached_environment = None # Test login response = test_client.post("/api/v1/admin/auth/login", json={ "username": "admin", "password": "admin123" }) # Check cookie has secure flag set_cookie_header = response.headers.get("set-cookie") assert "Secure" in set_cookie_header ``` ### Debugging Environment Detection #### Print Current Environment ```python # Add to your startup or debug endpoint from app.core.environment import get_environment, should_use_secure_cookies @app.on_event("startup") async def startup_event(): env = get_environment() secure = should_use_secure_cookies() print(f"🌍 Environment: {env}") print(f"🔒 Secure cookies: {secure}") print(f"📍 Running on: {os.getenv('HOSTNAME', 'localhost')}") ``` #### Debug Endpoint (Development Only) ```python from app.core.environment import get_environment, is_development import os @router.get("/debug/environment") def debug_environment(): """Debug endpoint to check environment detection.""" if not is_development(): raise HTTPException(status_code=404) return { "detected_environment": get_environment(), "should_use_secure_cookies": should_use_secure_cookies(), "env_var_ENV": os.getenv("ENV"), "env_var_ENVIRONMENT": os.getenv("ENVIRONMENT"), "env_var_DEBUG": os.getenv("DEBUG"), "hostname": os.getenv("HOSTNAME", "unknown"), } ``` --- ## Deployment Guide ### Local Development **Setup:** ```bash # Clone repo git clone cd orion-repo # Create virtual environment python -m venv venv source venv/bin/activate # or `venv\Scripts\activate` on Windows # Install dependencies pip install -r requirements.txt # Run application (no environment config needed!) uvicorn main:app --reload ``` **Access:** - Admin: http://localhost:8000/admin - Store: http://localhost:8000/store/{code} - Shop: http://localhost:8000/shop **Environment:** - Auto-detected as "development" - Cookies work with HTTP - Debug mode enabled ### Staging Deployment **Docker Compose:** ```yaml version: '3.8' services: web: build: . environment: - ENV=staging - DATABASE_URL=postgresql://user:pass@db:5432/orion_staging ports: - "8000:8000" depends_on: - db db: image: postgres:15 environment: - POSTGRES_DB=orion_staging - POSTGRES_USER=user - POSTGRES_PASSWORD=pass ``` **Kubernetes:** ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: orion-staging spec: template: spec: containers: - name: web image: orion:latest env: - name: ENV value: "staging" - name: DATABASE_URL valueFrom: secretKeyRef: name: db-credentials key: url ``` **Requirements:** - HTTPS enabled (nginx/traefik) - SSL certificates configured - Environment variable set: `ENV=staging` ### Production Deployment **systemd Service:** ```ini [Unit] Description=Orion Platform After=network.target [Service] User=orion WorkingDirectory=/opt/orion Environment="ENV=production" Environment="DATABASE_URL=postgresql://user:pass@localhost/orion_prod" ExecStart=/opt/orion/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 Restart=always [Install] WantedBy=multi-user.target ``` **Docker:** ```dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . ENV ENV=production ENV PYTHONUNBUFFERED=1 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"] ``` **Requirements:** - ✅ HTTPS enabled and enforced - ✅ SSL certificates valid - ✅ Environment variable: `ENV=production` - ✅ Database backups configured - ✅ Monitoring and logging enabled - ✅ Security headers configured ### Environment Variable Checklist | Environment | ENV Variable | HTTPS Required | Debug Mode | Cookie Secure | |-------------|-------------|----------------|------------|---------------| | Development | Not set (or "development") | ❌ No | ✅ Yes | ❌ No | | Staging | "staging" | ✅ Yes | ⚠️ Limited | ✅ Yes | | Production | "production" | ✅ Yes | ❌ No | ✅ Yes | --- ## Testing ### Manual Testing #### Test Environment Detection ```bash # Test development (default) python -c " from app.core.environment import get_environment, should_use_secure_cookies print(f'Environment: {get_environment()}') print(f'Secure cookies: {should_use_secure_cookies()}') " # Expected output: # Environment: development # Secure cookies: False ``` ```bash # Test production ENV=production python -c " from app.core.environment import get_environment, should_use_secure_cookies print(f'Environment: {get_environment()}') print(f'Secure cookies: {should_use_secure_cookies()}') " # Expected output: # Environment: production # Secure cookies: True ``` #### Test Cookie Behavior **Development:** ```bash # Start server (development mode) uvicorn main:app --reload # Login and check cookie curl -X POST http://localhost:8000/api/v1/admin/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}' \ -v 2>&1 | grep -i "set-cookie" # Should see: Set-Cookie: admin_token=...; HttpOnly; SameSite=Lax; Path=/admin # Should NOT see: Secure flag ``` **Production:** ```bash # Start server (production mode) ENV=production uvicorn main:app # Login and check cookie curl -X POST https://yourdomain.com/api/v1/admin/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}' \ -v 2>&1 | grep -i "set-cookie" # Should see: Set-Cookie: admin_token=...; Secure; HttpOnly; SameSite=Lax; Path=/admin # Should see: Secure flag present ``` ### Automated Tests #### pytest Examples ```python # tests/test_environment.py import pytest import os from app.core.environment import ( get_environment, is_development, is_staging, is_production, should_use_secure_cookies ) @pytest.fixture(autouse=True) def clear_environment_cache(): """Clear cached environment before each test.""" import app.core.environment as env_module env_module._cached_environment = None yield env_module._cached_environment = None class TestEnvironmentDetection: def test_default_is_development(self): """Test that default environment is development.""" # Clear any env vars os.environ.pop("ENV", None) os.environ.pop("ENVIRONMENT", None) assert get_environment() == "development" assert is_development() == True assert is_production() == False assert should_use_secure_cookies() == False def test_env_variable_production(self, monkeypatch): """Test ENV=production detection.""" monkeypatch.setenv("ENV", "production") assert get_environment() == "production" assert is_production() == True assert should_use_secure_cookies() == True def test_environment_variable_staging(self, monkeypatch): """Test ENVIRONMENT=staging detection.""" monkeypatch.setenv("ENVIRONMENT", "staging") assert get_environment() == "staging" assert is_staging() == True assert should_use_secure_cookies() == True def test_env_takes_priority_over_environment(self, monkeypatch): """Test that ENV variable has priority.""" monkeypatch.setenv("ENV", "production") monkeypatch.setenv("ENVIRONMENT", "staging") # ENV should win assert get_environment() == "production" def test_debug_true_is_development(self, monkeypatch): """Test that DEBUG=true results in development.""" monkeypatch.delenv("ENV", raising=False) monkeypatch.delenv("ENVIRONMENT", raising=False) monkeypatch.setenv("DEBUG", "true") assert get_environment() == "development" class TestCookieSecurity: def test_secure_cookies_in_production(self, monkeypatch): """Test cookies are secure in production.""" monkeypatch.setenv("ENV", "production") assert should_use_secure_cookies() == True def test_insecure_cookies_in_development(self): """Test cookies are not secure in development.""" os.environ.pop("ENV", None) assert should_use_secure_cookies() == False # tests/test_authentication_cookies.py def test_login_cookie_matches_environment(test_client, monkeypatch): """Test that login cookie security matches environment.""" # Test development monkeypatch.delenv("ENV", raising=False) response = test_client.post("/api/v1/admin/auth/login", json={ "username": "admin", "password": "admin123" }) set_cookie = response.headers.get("set-cookie", "") assert "Secure" not in set_cookie # No Secure in development # Test production monkeypatch.setenv("ENV", "production") # Clear cache import app.core.environment as env_module env_module._cached_environment = None response = test_client.post("/api/v1/admin/auth/login", json={ "username": "admin", "password": "admin123" }) set_cookie = response.headers.get("set-cookie", "") assert "Secure" in set_cookie # Secure in production ``` --- ## Best Practices ### DO ✅ 1. **Use the environment utility functions:** ```python from app.core.environment import should_use_secure_cookies response.set_cookie(secure=should_use_secure_cookies()) ``` 2. **Set ENV in production/staging:** ```bash export ENV=production ``` 3. **Use cached version for frequent calls:** ```python from app.core.environment import get_cached_environment env = get_cached_environment() # Cached - faster ``` 4. **Keep environment logic in environment.py:** - Add new environment-dependent functions to `environment.py` - Don't scatter environment checks throughout codebase 5. **Document environment-dependent behavior:** ```python def send_email(to: str, subject: str): """ Send email to recipient. Environment behavior: - Development: Logs email to console (no actual send) - Staging: Sends to test email address - Production: Sends to actual recipient """ if is_development(): logger.info(f"[DEV] Email to {to}: {subject}") elif is_staging(): actual_to = f"staging+{to}@example.com" send_via_smtp(actual_to, subject) else: send_via_smtp(to, subject) ``` 6. **Test with different environments:** ```python @pytest.mark.parametrize("env", ["development", "staging", "production"]) def test_feature_in_all_environments(env, monkeypatch): monkeypatch.setenv("ENV", env) # Clear cache import app.core.environment as env_module env_module._cached_environment = None # Test your feature ``` ### DON'T ❌ 1. **Don't hardcode environment checks:** ```python # ❌ Bad if os.getenv("ENV") == "production": use_https = True # ✅ Good use_https = should_use_secure_cookies() ``` 2. **Don't assume environment:** ```python # ❌ Bad - assumes production response.set_cookie(secure=True) # ✅ Good - adapts to environment response.set_cookie(secure=should_use_secure_cookies()) ``` 3. **Don't bypass environment detection:** ```python # ❌ Bad FORCE_PRODUCTION = True if FORCE_PRODUCTION: ... # ✅ Good - use environment variable # export ENV=production if is_production(): ... ``` 4. **Don't mix environment checking methods:** ```python # ❌ Bad - inconsistent if os.getenv("ENV") == "prod": log_level = "WARNING" elif is_development(): log_level = "DEBUG" # ✅ Good - consistent env = get_environment() if env == "production": log_level = "WARNING" elif env == "development": log_level = "DEBUG" ``` 5. **Don't forget to clear cache in tests:** ```python # ❌ Bad - cache may interfere def test_production(): os.environ["ENV"] = "production" assert get_environment() == "production" # May fail if cached # ✅ Good - clear cache def test_production(): import app.core.environment as env_module env_module._cached_environment = None os.environ["ENV"] = "production" assert get_environment() == "production" ``` ### Security Considerations 1. **Always use HTTPS in production:** - Environment detection enables `secure=True` for cookies - But you must configure HTTPS at the server level 2. **Never disable security in production:** ```python # ❌ NEVER do this if is_production(): response.set_cookie(secure=False) # Security vulnerability! ``` 3. **Validate environment variable values:** - The system only accepts: "development", "staging", "production" - Invalid values default to "development" (safe) 4. **Monitor environment in production:** ```python @app.on_event("startup") async def check_production_settings(): if is_production(): # Verify production requirements assert os.getenv("DATABASE_URL"), "DATABASE_URL required in production" assert os.getenv("SECRET_KEY"), "SECRET_KEY required in production" logger.info("✅ Production environment verified") ``` --- ## Troubleshooting ### Issue: Cookies Not Working **Symptom:** Authentication cookies not being sent/received **Check:** 1. **Environment detection:** ```python from app.core.environment import get_environment, should_use_secure_cookies print(f"Environment: {get_environment()}") print(f"Secure cookies: {should_use_secure_cookies()}") ``` 2. **Development (HTTP):** - `should_use_secure_cookies()` should return `False` - Access via `http://localhost:8000` (not HTTPS) 3. **Production (HTTPS):** - `should_use_secure_cookies()` should return `True` - Access via `https://yourdomain.com` (with HTTPS) - Verify SSL certificate is valid ### Issue: Wrong Environment Detected **Symptom:** System detects wrong environment **Solution:** ```bash # Explicitly set environment export ENV=production # Restart application uvicorn main:app --reload # Verify python -c "from app.core.environment import get_environment; print(get_environment())" ``` ### Issue: Environment Not Changing **Symptom:** Environment stuck on old value **Cause:** Environment is cached **Solution:** ```python # Clear cache and re-detect import app.core.environment as env_module env_module._cached_environment = None # Now check again from app.core.environment import get_environment print(get_environment()) ``` ### Issue: Tests Failing Due to Environment **Symptom:** Tests pass locally but fail in CI/CD **Solution:** Always clear cache in test fixtures: ```python @pytest.fixture(autouse=True) def clear_environment_cache(): """Clear environment cache before each test.""" import app.core.environment as env_module env_module._cached_environment = None yield env_module._cached_environment = None ``` --- ## Summary ### Quick Reference ```python # Import from app.core.environment import ( get_environment, is_development, is_production, should_use_secure_cookies ) # Usage env = get_environment() # "development" | "staging" | "production" if is_development(): # Development-only code pass if is_production(): # Production-only code pass # Cookie security (most common use case) response.set_cookie(secure=should_use_secure_cookies()) ``` ### Configuration | Environment | Set Variable | Cookie Secure | HTTPS Required | |-------------|--------------|---------------|----------------| | Development | Not needed (default) | False | No | | Staging | `ENV=staging` | True | Yes | | Production | `ENV=production` | True | Yes | ### Key Points 1. ✅ **Development works out of the box** - No configuration needed 2. ✅ **Production requires explicit setting** - Set `ENV=production` 3. ✅ **Use utility functions** - Don't check environment directly 4. ✅ **HTTPS required in production** - Configure at server level 5. ✅ **Test with different environments** - Use monkeypatch in tests --- ## Additional Resources ### Related Documentation - [Authentication System Documentation](../api/authentication.md) - [Deployment Guide](../deployment/production.md) - [Exception Handling](exception-handling.md) ### External References - [FastAPI Security](https://fastapi.tiangolo.com/tutorial/security/) - [HTTP Cookies (MDN)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) - [12-Factor App: Config](https://12factor.net/config) --- **Questions?** Contact the backend team or check the main documentation. **End of Document**