Skip to content

Deployment Architecture


Production Stack

flowchart LR
    GitHub --> GHA[GitHub Actions\nCI/CD]
    GHA --> Render[Render\nWeb Service]
    Render --> Gunicorn[Gunicorn\n1 worker 6 threads]
    Gunicorn --> Flask[Flask App]
    Flask --> Postgres[(PostgreSQL\nRender Managed)]
    Flask --> Wasabi[Wasabi S3\nAP Southeast-1]
    Flask --> Redis[(Redis\nCelery broker)]

Render Configuration

Setting Value
Plan Starter (512 MB RAM, shared CPU)
Region Singapore (closest to India)
Start command Procfileweb: gunicorn ...
DB PostgreSQL (Render managed)
Auto-deploy On push to main via GitHub Actions

Gunicorn Configuration (Procfile)

web: gunicorn Lenzeye:app \ --workers=1 \ --threads=6 \ --timeout=120 \ --graceful-timeout=30 \ --keep-alive=5 \ --worker-class=gthread \ --worker-tmp-dir=/dev/shm \ --log-level=info \ --limit-request-line=4096 \ --limit-request-field_size=8190 \ --max-requests=1000 \ --max-requests-jitter=50

Parameter Value Reason
--workers=1 1 Avoid RAM duplication across processes
--threads=6 6 Handle concurrent requests in one process
--timeout=120 120s Allow large encrypted uploads to complete
--worker-class=gthread gthread Thread-based, ideal for I/O-heavy workloads
--worker-tmp-dir=/dev/shm RAM-backed Fast worker heartbeat writes
--max-requests=1000 1000 + jitter Restart workers periodically to prevent memory drift

Do not change --workers=1 without re-validating RAM

The validated production configuration runs 1 worker. Multiple workers each load the full app (models, blueprints, lazy imports) independently, doubling RAM usage. Peak RAM is already 398 MB on a 512 MB plan.


WSGI Entry Point

wsgi.py: python from Lenzeye import create_app app = create_app()

Lenzeye.py contains create_app() which: 1. Creates Flask app instance 2. Loads environment variables 3. Configures SQLAlchemy with correct DB URL 4. Registers all 15+ blueprints 5. Initializes Celery, CORS, Migrate


Environment Separation

Environment DB Config file
Local dev SQLite (DSS_local.db) sendgrid.env
Production PostgreSQL (Render) render.env

Detection: os.getenv("RENDER") — Render sets this automatically.

python if os.getenv("RENDER"): load_dotenv("render.env") else: load_dotenv("sendgrid.env")


Database Connection

python engine_options = { 'pool_pre_ping': True, # Verify connections before use 'pool_recycle': 180, # Recycle connections every 3 min 'pool_timeout': 30, # Fail fast if pool exhausted 'connect_args': {'connect_timeout': 10} # PostgreSQL only }

USE_INTERNAL_DB env var switches between Render internal and external DB URLs (internal is faster, no SSL overhead).


Wasabi S3 Configuration

python s3 = boto3.client( 's3', endpoint_url='https://s3.ap-southeast-1.wasabisys.com', region_name='ap-southeast-1', aws_access_key_id=access_key, aws_secret_access_key=secret_key, config=Config( signature_version='s3v4', max_pool_connections=50, retries={'max_attempts': 3, 'mode': 'adaptive'}, connect_timeout=10, read_timeout=60 ) )


TL;DR

Server: Render Starter plan, Singapore region. 1 Gunicorn worker, 6 gthread threads, 120s timeout. DB: PostgreSQL (Render managed). SQLite locally. Storage: Wasabi S3 AP Southeast-1, 50-connection pool, adaptive retry. Deploy: GitHub push → GitHub Actions → Render auto-deploy. Env separation: RENDER env var selects production config.