# Gunicorn Configuration File for Trasabilitate Application # Docker-optimized Production WSGI server configuration import multiprocessing import os # ============================================================================ # SERVER SOCKET CONFIGURATION # ============================================================================ # Bind to all interfaces on port from environment or default bind = os.getenv("GUNICORN_BIND", "0.0.0.0:8781") backlog = int(os.getenv("GUNICORN_BACKLOG", "2048")) # ============================================================================ # WORKER PROCESSES CONFIGURATION # ============================================================================ # Calculate workers: For Docker, use CPU count * 2 + 1 (but allow override) # In Docker, cpu_count() returns container CPU limit if set workers = int(os.getenv("GUNICORN_WORKERS", multiprocessing.cpu_count() * 2 + 1)) # Worker class - 'sync' is stable for most use cases # Alternative: 'gevent' or 'gthread' for better concurrency worker_class = os.getenv("GUNICORN_WORKER_CLASS", "sync") # Max simultaneous connections per worker worker_connections = int(os.getenv("GUNICORN_WORKER_CONNECTIONS", "1000")) # Workers silent for more than this many seconds are killed and restarted # Increase for long-running requests (file uploads, reports) timeout = int(os.getenv("GUNICORN_TIMEOUT", "120")) # Keep-alive for reusing connections keepalive = int(os.getenv("GUNICORN_KEEPALIVE", "5")) # Graceful timeout - time to wait for workers to finish during shutdown graceful_timeout = int(os.getenv("GUNICORN_GRACEFUL_TIMEOUT", "30")) # ============================================================================ # WORKER LIFECYCLE - PREVENT MEMORY LEAKS # ============================================================================ # Restart workers after this many requests to prevent memory leaks max_requests = int(os.getenv("GUNICORN_MAX_REQUESTS", "1000")) max_requests_jitter = int(os.getenv("GUNICORN_MAX_REQUESTS_JITTER", "100")) # ============================================================================ # LOGGING CONFIGURATION # ============================================================================ # Docker-friendly: logs to stdout/stderr by default, but allow file logging accesslog = os.getenv("GUNICORN_ACCESS_LOG", "/srv/quality_recticel/logs/access.log") errorlog = os.getenv("GUNICORN_ERROR_LOG", "/srv/quality_recticel/logs/error.log") # For pure Docker logging (12-factor app), use: # accesslog = "-" # stdout # errorlog = "-" # stderr loglevel = os.getenv("GUNICORN_LOG_LEVEL", "info") # Enhanced access log format with timing and user agent access_log_format = ( '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s ' '"%(f)s" "%(a)s" %(D)s ยตs' ) # Capture stdout/stderr in log (useful for print statements) capture_output = os.getenv("GUNICORN_CAPTURE_OUTPUT", "true").lower() == "true" # ============================================================================ # PROCESS NAMING & DAEMON # ============================================================================ proc_name = os.getenv("GUNICORN_PROC_NAME", "trasabilitate_app") # CRITICAL FOR DOCKER: Never use daemon mode in containers # Docker needs the process to run in foreground daemon = False # ============================================================================ # SECURITY & LIMITS # ============================================================================ # Request line size limit (protect against large headers) limit_request_line = int(os.getenv("GUNICORN_LIMIT_REQUEST_LINE", "4094")) limit_request_fields = int(os.getenv("GUNICORN_LIMIT_REQUEST_FIELDS", "100")) limit_request_field_size = int(os.getenv("GUNICORN_LIMIT_REQUEST_FIELD_SIZE", "8190")) # ============================================================================ # PERFORMANCE OPTIMIZATION # ============================================================================ # Preload application before forking workers # Pros: Faster worker spawn, less memory if using copy-on-write # Cons: Code changes require full restart preload_app = os.getenv("GUNICORN_PRELOAD_APP", "true").lower() == "true" # Pseudo-random number for load balancing worker_tmp_dir = os.getenv("GUNICORN_WORKER_TMP_DIR", "/dev/shm") # ============================================================================ # SSL CONFIGURATION (if needed) # ============================================================================ # Uncomment and set environment variables if using HTTPS # keyfile = os.getenv("SSL_KEY_FILE") # certfile = os.getenv("SSL_CERT_FILE") # ca_certs = os.getenv("SSL_CA_CERTS") # ============================================================================ # SERVER HOOKS - LIFECYCLE CALLBACKS # ============================================================================ def on_starting(server): """Called just before the master process is initialized.""" server.log.info("=" * 60) server.log.info("๐Ÿš€ Trasabilitate Application - Starting Server") server.log.info("=" * 60) server.log.info("๐Ÿ“ Configuration:") server.log.info(f" โ€ข Workers: {workers}") server.log.info(f" โ€ข Worker Class: {worker_class}") server.log.info(f" โ€ข Timeout: {timeout}s") server.log.info(f" โ€ข Bind: {bind}") server.log.info(f" โ€ข Preload App: {preload_app}") server.log.info(f" โ€ข Max Requests: {max_requests} (+/- {max_requests_jitter})") server.log.info("=" * 60) def when_ready(server): """Called just after the server is started.""" server.log.info("=" * 60) server.log.info("โœ… Trasabilitate Application Server is READY!") server.log.info(f"๐Ÿ“ก Listening on: {server.address}") server.log.info(f"๐ŸŒ Access the application at: http://{bind}") server.log.info("=" * 60) def on_exit(server): """Called just before exiting Gunicorn.""" server.log.info("=" * 60) server.log.info("๐Ÿ‘‹ Trasabilitate Application - Shutting Down") server.log.info("=" * 60) def worker_int(worker): """Called just after a worker exited on SIGINT or SIGQUIT.""" worker.log.info("โš ๏ธ Worker %s received INT or QUIT signal", worker.pid) def pre_fork(server, worker): """Called just before a worker is forked.""" server.log.info("๐Ÿ”„ Forking new worker (pid: %s)", worker.pid) def post_fork(server, worker): """Called just after a worker has been forked.""" server.log.info("โœจ Worker spawned successfully (pid: %s)", worker.pid) def pre_exec(server): """Called just before a new master process is forked.""" server.log.info("๐Ÿ”„ Master process forking...") def worker_abort(worker): """Called when a worker received the SIGABRT signal.""" worker.log.warning("๐Ÿšจ Worker %s received SIGABRT signal - ABORTING!", worker.pid) def child_exit(server, worker): """Called just after a worker has been exited, in the master process.""" server.log.info("๐Ÿ‘‹ Worker %s exited (exit code: %s)", worker.pid, worker.tmp.last_mtime)