From 9931262370a978cb34c37463c83cd344c0581697 Mon Sep 17 00:00:00 2001
From: Stefan Fleckenstein <stefan.fleckenstein@maibornwolff.de>
Date: Fri, 15 Dec 2023 07:25:34 +0100
Subject: [PATCH] chore: make workers and threads for Gunicorn configurable

---
 docker-compose-playwright.yml         |  3 +++
 docker-compose-prod-mysql.yml         |  3 +++
 docker-compose-prod-postgres.yml      |  3 +++
 docker-compose-prod-test.yml          |  3 +++
 docker/backend/prod/django/entrypoint | 13 +++++++++++--
 docs/getting_started/configuration.md | 14 ++++++++------
 6 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/docker-compose-playwright.yml b/docker-compose-playwright.yml
index 17035937c..df24c0fd5 100644
--- a/docker-compose-playwright.yml
+++ b/docker-compose-playwright.yml
@@ -34,6 +34,9 @@ services:
       ADMIN_USER: admin-user
       ADMIN_PASSWORD: admin-password
       ADMIN_EMAIL: admin@example.com
+      # --- Gunicorn ---
+      GUNICORN_WORKERS: 3
+      GUNICORN_THREADS: 10
       # --- Database ---
       DATABASE_ENGINE: django.db.backends.sqlite3
       # --- Security ---
diff --git a/docker-compose-prod-mysql.yml b/docker-compose-prod-mysql.yml
index 088c6cf42..5f8ca8904 100644
--- a/docker-compose-prod-mysql.yml
+++ b/docker-compose-prod-mysql.yml
@@ -67,6 +67,9 @@ services:
       ADMIN_USER: ${SO_ADMIN_USER:-admin}
       ADMIN_PASSWORD: ${SO_ADMIN_PASSWORD:-admin}
       ADMIN_EMAIL: ${SO_ADMIN_EMAIL:-admin@example.com}
+      # --- Gunicorn ---
+      GUNICORN_WORKERS: ${SO_GUNICORN_WORKERS:-3}
+      GUNICORN_THREADS: ${SO_GUNICORN_THREADS:-10}
       # --- Database ---
       DATABASE_ENGINE: ${SO_DATABASE_ENGINE:-django.db.backends.mysql}
       DATABASE_HOST: ${SO_DATABASE_HOST:-mysql}
diff --git a/docker-compose-prod-postgres.yml b/docker-compose-prod-postgres.yml
index 1affac7c4..77960bea6 100644
--- a/docker-compose-prod-postgres.yml
+++ b/docker-compose-prod-postgres.yml
@@ -67,6 +67,9 @@ services:
       ADMIN_USER: ${SO_ADMIN_USER:-admin}
       ADMIN_PASSWORD: ${SO_ADMIN_PASSWORD:-admin}
       ADMIN_EMAIL: ${SO_ADMIN_EMAIL:-admin@example.com}
+      # --- Gunicorn ---
+      GUNICORN_WORKERS: ${SO_GUNICORN_WORKERS:-3}
+      GUNICORN_THREADS: ${SO_GUNICORN_THREADS:-10}
       # --- Database ---
       DATABASE_ENGINE: ${SO_DATABASE_ENGINE:-django.db.backends.postgresql}
       DATABASE_HOST: ${SO_DATABASE_HOST:-postgres}
diff --git a/docker-compose-prod-test.yml b/docker-compose-prod-test.yml
index e87be6ed9..84fcf8130 100644
--- a/docker-compose-prod-test.yml
+++ b/docker-compose-prod-test.yml
@@ -36,6 +36,9 @@ services:
       ADMIN_USER: ${SO_ADMIN_USER:-admin}
       ADMIN_PASSWORD: ${SO_ADMIN_PASSWORD:-admin}
       ADMIN_EMAIL: ${SO_ADMIN_EMAIL:-admin@example.com}
+      # --- Gunicorn ---
+      GUNICORN_WORKERS: ${SO_GUNICORN_WORKERS:-3}
+      GUNICORN_THREADS: ${SO_GUNICORN_THREADS:-10}
       # --- Database ---
       DATABASE_ENGINE: ${SO_DATABASE_ENGINE:-django.db.backends.postgresql}
       DATABASE_HOST: ${SO_DATABASE_HOST:-postgres}
diff --git a/docker/backend/prod/django/entrypoint b/docker/backend/prod/django/entrypoint
index 098b03f9f..a2420efaa 100644
--- a/docker/backend/prod/django/entrypoint
+++ b/docker/backend/prod/django/entrypoint
@@ -1,7 +1,6 @@
 #!/bin/sh
 
 set -o errexit
-set -o nounset
 
 if [ "${DATABASE_ENGINE}" != "django.db.backends.sqlite3" ]; then
     if [ -z "${DATABASE_USER}" ]; then
@@ -49,4 +48,14 @@ python /app/manage.py collectstatic --noinput
 
 python manage.py run_huey --flush-locks &
 
-gunicorn config.wsgi --bind 0.0.0.0:5000 --timeout 1200 --chdir=/app -w 2 -k gthread --threads 10
+if [ -z "$GUNICORN_WORKERS" ]; then
+    echo "GUNICORN_WORKERS is not set, defaulting to 3"
+    export GUNICORN_WORKERS=3
+fi
+
+if [ -z "${GUNICORN_THREADS}" ]; then
+    echo "GUNICORN_THREADS is not set, defaulting to 10"
+    GUNICORN_THREADS=10
+fi
+
+gunicorn config.wsgi --bind 0.0.0.0:5000 --timeout 1200 --chdir=/app -w $GUNICORN_WORKERS -k gthread --threads $GUNICORN_THREADS
diff --git a/docs/getting_started/configuration.md b/docs/getting_started/configuration.md
index 46ae23a90..ebe2c3232 100644
--- a/docs/getting_started/configuration.md
+++ b/docs/getting_started/configuration.md
@@ -22,13 +22,15 @@ A part of the configuation is done with environment variables, which need to be
 | `MYSQL_AZURE`          | optional    | Must be set if Azure Database for MySQL is used, to use the necessary SSL certificate. For **MySQL Flexible Server** it needs to have the value `flexible`, for **MySQL Single Server** the the value needs to be `single`. See [Connect using mysql command-line client with TLS/SSL](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-connect-tls-ssl#connect-using-mysql-command-line-client-with-tlsssl) and [Configure SSL connectivity in your application to securely connect to Azure Database for MySQL](https://learn.microsoft.com/en-us/azure/mysql/single-server/how-to-configure-ssl#step-1-obtain-ssl-certificate).
 | `DJANGO_SECRET_KEY`    | mandatory   | A secret key for a particular Django installation. This is used to provide cryptographic signing, and should be set to a unique, unpredictable value with at least 50 characters, see [Django settings SECRET_KEY](https://docs.djangoproject.com/en/3.2/ref/settings/#secret-key).
 | `FIELD_ENCRYPTION_KEY` | mandatory   | Key to encrypt fields like the JWT secret. See [Generating an Encryption Key](https://gitlab.com/lansharkconsulting/django/django-encrypted-model-fields#generating-an-encryption-key) how to generate the key. |
-| `OIDC_AUTHORITY`     | mandatory   | The authority is a URL that hosts the OpenID configuration well-known endpoint.  |
-| `OIDC_CLIENT_ID`     | mandatory   | The client ID is the unique *Application (client) ID* assigned to your app by the OpenID Connect provider when the app
+| `GUNICORN_WORKERS`     | optional   | Number of worker processes for the Gunicorn web server, see [Gunicorn documentation](https://docs.gunicorn.org/en/stable/design.html#how-many-workers). Default is 3. |
+| `GUNICORN_THREADS`     | optional   | Number of worker threads for the Gunicorn web server, default is 10. |
+| `OIDC_AUTHORITY`       | mandatory   | The authority is a URL that hosts the OpenID configuration well-known endpoint.  |
+| `OIDC_CLIENT_ID`       | mandatory   | The client ID is the unique *Application (client) ID* assigned to your app by the OpenID Connect provider when the app
 was registered. |
-| `OIDC_USERNAME`      | mandatory   | The claim that contains the username to find or create the user. |
-| `OIDC_FIRST_NAME`    | mandatory   | The claim that contains the first name of the user. |
-| `OIDC_LAST_NAME`     | mandatory   | The claim that contains the last name of the user. |
-| `OIDC_EMAIL`         | mandatory   | The claim that contains the email address of the user. |
+| `OIDC_USERNAME`        | mandatory   | The claim that contains the username to find or create the user. |
+| `OIDC_FIRST_NAME`      | mandatory   | The claim that contains the first name of the user. |
+| `OIDC_LAST_NAME`       | mandatory   | The claim that contains the last name of the user. |
+| `OIDC_EMAIL`           | mandatory   | The claim that contains the email address of the user. |
 
 
 #### Frontend