diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 536b3cc9..b06254bd 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -164,8 +164,39 @@ python manage.py setup_admin_groups # python manage.py rename_talk_files # Start server -echo "Starting server" +# +# Production-grade environments (TEST, PROD) run Gunicorn, the recommended WSGI +# server. Local development (DJANGO_ENV=DEBUG) keeps Django's `runserver` for +# its auto-reload on code edits, friendlier tracebacks, debug toolbar, and +# static-file serving under DEBUG=True. See issue #1034. +# +# This swap is entirely inside the container -- UW CSE's Apache still reverse- +# proxies dynamic requests to 127.0.0.1:8571 (-> container :8000) and serves +# /static/ and /media/ directly, exactly as before -- so it ships via the +# normal push-to-deploy path with no Apache/IT changes. +# +# Gunicorn tuning (overridable via env vars in the compose file): +# GUNICORN_WORKERS number of worker processes. The (2*cores)+1 rule of thumb +# would be ~49 on the 24-core host, but that box is SHARED +# with all Project Sidewalk instances (see #959), so we +# default to a modest 3. +# GUNICORN_TIMEOUT per-request worker timeout in seconds. Gunicorn's default +# of 30s can kill slow admin operations (ImageMagick/PDF +# thumbnail generation), so we default to 120. echo "****************** STEP 5/5: docker-entrypoint.sh ************************" -echo "5. Starting server with 'python manage.py runserver 0.0.0.0:8000'" -echo "******************************************" -python manage.py runserver 0.0.0.0:8000 \ No newline at end of file +if [ "$DJANGO_ENV" = "TEST" ] || [ "$DJANGO_ENV" = "PROD" ]; then + GUNICORN_WORKERS="${GUNICORN_WORKERS:-3}" + GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-120}" + echo "5. Starting Gunicorn (DJANGO_ENV=$DJANGO_ENV, workers=$GUNICORN_WORKERS, timeout=${GUNICORN_TIMEOUT}s)" + echo "******************************************" + exec gunicorn makeabilitylab.wsgi:application \ + --bind 0.0.0.0:8000 \ + --workers "$GUNICORN_WORKERS" \ + --timeout "$GUNICORN_TIMEOUT" \ + --access-logfile - \ + --error-logfile - +else + echo "5. Starting dev server with 'python manage.py runserver 0.0.0.0:8000' (DJANGO_ENV=$DJANGO_ENV)" + echo "******************************************" + exec python manage.py runserver 0.0.0.0:8000 +fi \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 337c609b..98dd810d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -116,6 +116,23 @@ django-sortedm2m==4.0.0 django-prose-editor[sanitize]==0.26.0 +# ----------------------------------------------------------------------------- +# WSGI Server (production) +# ----------------------------------------------------------------------------- +# Gunicorn is the production WSGI server. We previously ran Django's dev +# `runserver` on test AND prod, which the Django docs explicitly warn against +# ("DO NOT USE THIS SERVER IN A PRODUCTION SETTING ... has not gone through +# security audits or performance tests"). See issue #1034. +# +# Gunicorn runs inside the same container behind UW CSE's Apache reverse proxy +# (Apache still serves /static/ and /media/ directly and proxies dynamic +# requests to 127.0.0.1:8571 -> container :8000), so this swap is contained to +# the container and ships via the normal push-to-deploy path -- no Apache or +# UW CSE IT changes required. Worker count and request timeout are tunable via +# the GUNICORN_WORKERS / GUNICORN_TIMEOUT env vars in docker-entrypoint.sh. +# See: https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/gunicorn/ +gunicorn==23.0.0 + # ----------------------------------------------------------------------------- # Security & Networking # -----------------------------------------------------------------------------