From 0d56daa664ef80983b36d82909e8593a9119ecaf Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 14 Aug 2025 13:34:29 -0400 Subject: [PATCH] implement gunicorn WSGI --- app/__init__.py | 3 +++ dockerfile | 24 ++++++------------ entrypoint.sh | 66 ++++++++++++++++++++++++++++-------------------- requirements.txt | 3 ++- 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index a3c10cf..b7c253c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,6 +4,7 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager +from werkzeug.middleware.proxy_fix import ProxyFix db = SQLAlchemy() @@ -22,6 +23,8 @@ def create_app(config_class='config.Config'): login_manager.init_app(app) login_manager.login_view = 'auth.login' + app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1) + from app.main import main from app.auth import auth diff --git a/dockerfile b/dockerfile index 9795a6d..7f9ffc2 100644 --- a/dockerfile +++ b/dockerfile @@ -1,31 +1,23 @@ # Dockerfile FROM python:slim -# Set work directory WORKDIR /app -# Install PostgreSQL client +# Only what you actually need (ffmpeg kept for video/thumbs) RUN apt-get update && \ - apt-get install -y postgresql-client ffmpeg && \ - apt-get clean && \ + apt-get install -y --no-install-recommends ffmpeg && \ rm -rf /var/lib/apt/lists/* -# Copy project files +# Install deps first for better layer caching +COPY requirements.txt . +RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the app COPY . . -# Install dependencies -RUN pip install --upgrade pip && pip install -r requirements.txt - -# Copy and prepare entrypoint script +# Entrypoint controls migrations, background worker, and Gunicorn COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -# Set environment variables -ENV FLASK_APP=run.py -ENV FLASK_RUN_HOST=0.0.0.0 - -# Expose port EXPOSE 5000 - -# Use entrypoint script ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index c9efc10..bf68a3d 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,36 +1,48 @@ -#!/bin/bash +#!/usr/bin/env sh +set -e -# Default to sqlite if not specified -DB_TYPE="${DB_TYPE:-sqlite}" +# Ensure Flask CLI finds your app +export FLASK_APP=run.py +export FLASK_ENV=production -# Wait for PostgreSQL if needed -if [ "$DB_TYPE" = "postgresql" ]; then - MAX_WAIT=180 - WAIT_INTERVAL=2 - WAITED=0 - echo "Database type is PostgreSQL. Waiting for database to be ready..." - until pg_isready -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" > /dev/null 2>&1; do - if [ $WAITED -ge $MAX_WAIT ]; then - echo "Timeout: Could not connect to the database at $DB_HOST:$DB_PORT within $MAX_WAIT seconds." +# --- Migrations with simple retry (no pg_isready needed) --- +if [ "${RUN_DB_MIGRATIONS:-1}" = "1" ]; then + ATTEMPTS="${MIGRATION_MAX_ATTEMPTS:-30}" # ~90s default with 3s sleep + SLEEP_SECS="${MIGRATION_RETRY_SLEEP:-3}" + i=1 + echo "Applying migrations (up to $ATTEMPTS attempts)..." + until flask db upgrade; do + if [ "$i" -ge "$ATTEMPTS" ]; then + echo "ERROR: Migrations failed after $ATTEMPTS attempts." exit 1 fi - echo "Waiting for database at $DB_HOST:$DB_PORT... ($WAITED/$MAX_WAIT seconds)" - sleep $WAIT_INTERVAL - WAITED=$((WAITED + WAIT_INTERVAL)) + echo "DB not ready yet; retrying in ${SLEEP_SECS}s ($i/$ATTEMPTS)…" + sleep "$SLEEP_SECS" + i=$((i+1)) done - echo "Database is ready." -else - echo "Database type is SQLite. Skipping PostgreSQL readiness check." fi -# Apply migrations -echo "Applying database migrations..." -flask db upgrade +# --- Start the image import worker exactly once per container --- +if [ "${RUN_IMAGE_IMPORTER:-1}" = "1" ]; then + echo "Starting image import worker (background)…" + python image_import_worker.py & +fi -# Start periodic image import in background -echo "import worker waiting for flag" -python image_import_worker.py & +# --- Start Gunicorn --- +GUNICORN_BIND="${GUNICORN_BIND:-0.0.0.0:5000}" +GUNICORN_WORKERS="${GUNICORN_WORKERS:-2}" +GUNICORN_THREADS="${GUNICORN_THREADS:-4}" +GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-120}" +GUNICORN_OPTS="${GUNICORN_OPTS:-}" -# Start the Flask app -echo "Starting Flask app..." -exec flask run --host=0.0.0.0 --port=5000 +echo "Starting Gunicorn on ${GUNICORN_BIND} …" +exec gunicorn \ + --bind "${GUNICORN_BIND}" \ + --workers "${GUNICORN_WORKERS}" \ + --worker-class gthread \ + --threads "${GUNICORN_THREADS}" \ + --timeout "${GUNICORN_TIMEOUT}" \ + --access-logfile - \ + --error-logfile - \ + ${GUNICORN_OPTS} \ + run:app diff --git a/requirements.txt b/requirements.txt index e5b2c3a..deb6047 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ psycopg2-binary email-validator pillow exifread -imagehash \ No newline at end of file +imagehash +gunicorn \ No newline at end of file