Migrate from uWSGI to gunicorn. Closes #891#904
Migrate from uWSGI to gunicorn. Closes #891#904regulartim merged 15 commits intoGreedyBear-Project:developfrom
Conversation
|
Hi @regulartim, I have completed the migration from uWSGI to gunicorn. Could you please review the PR when you get a chance? Also, I noticed the documentation at https://intelowlproject.github.io/docs/GreedyBear/Contribute/ still references the old
|
regulartim
left a comment
There was a problem hiding this comment.
Hey @SupRaKoshti ! Thanks for your work. Aside from the other comments, I checked the Gunicorn guide for docker deployments. They do some things a little different and I think we should match that in our default.yml:
What do you think of that?
|
|
||
| # separation is required to avoid to re-execute os installation in case of change of python requirements | ||
| RUN mkdir -p ${LOG_PATH}/django ${LOG_PATH}/uwsgi \ | ||
| RUN mkdir -p ${LOG_PATH}/django \ |
There was a problem hiding this comment.
Where will gunicorn create its logs ?
There was a problem hiding this comment.
The ${LOG_PATH}/uwsgi directory was specifically created for uWSGI logs.
Since we are removing uWSGI, this directory is no longer needed.
Gunicorn logs go to stdout/stderr by default, so no separate log directory is required for gunicorn.
Should I keep it that way or create a specific log directory?
There was a problem hiding this comment.
I think we should create a specific log directory for gunicorn, the same way we did for uwsgi.
|
There is also support for the uWSGI Protocol in Gunicorn, which seems to have advantages over http. Should be maybe use that? |
Hi @regulartim , that sounds great! I will look into the gunicorn Docker deployment guide and implement dynamic worker count calculation, health checks, and graceful shutdown. Give me some time to read through the documentation and I will update the PR accordingly. |
|
Hey @SupRaKoshti ! :) Please also take a look at the uwsgi protocol support I mentioned:
If I understand it correctly using it would be more efficient and result in a smaller diff (=less changes to the code). |
Yeah, I'll just remove the command, it is outdated anyway. |
… into feature/migrate-uwsgi-to-gunicorn t exit quit
Docker & Compose: - renamed service from `uwsgi` to `app` in default.yml and updated all references in nginx depends_on and qcluster depends_on Gunicorn configuration: - added dynamic worker count using $(( 2 * $(nproc) + 1 )) - added graceful shutdown with --graceful-timeout 30 and --timeout 120 - added stop_grace_period: 30s to app service to match graceful timeout - enabled uWSGI binary protocol via --protocol uwsgi flag - added custom gunicorn log directory (/var/log/greedybear/gunicorn) Nginx: - updated http.conf and https.conf to use uwsgi_pass instead of proxy_pass and uwsgi_cache instead of proxy_cache to match gunicorn uWSGI binary protocol - updated upstream server name from uwsgi:8001 to app:8001 - django_server.conf kept as proxy_pass (used only in local dev with Django runserver which speaks plain HTTP) Health checks: - replaced curl HTTP healthcheck with Python TCP socket check in both default.yml and Dockerfile since port 8001 now speaks uWSGI binary protocol, not HTTP - updated Dockerfile comment accordingly gbctl script: - updated all container name references from greedybear_gunicorn to greedybear_app in cmd_logs, cmd_health, cmd_create_admin and check_downgrade functions
|
Hey @regulartim! I have gone through the official Gunicorn documentation for Docker deployment and uWSGI protocol and implemented everything you suggested. Here's a summary of what was done: Dynamic Worker Count
Health Checks
Graceful Shutdown
uWSGI Protocol
Gunicorn Log Directory
Service Renamed
Ready for review! 🙏 |
|
Also @regulartim, I noticed that the Should we implement the non-root user for the Pros:
Cons / Risks:
Happy to implement this in a follow-up PR if you think it's worth doing! |
Yeah, I also thought about this. I would prefer to do that in a separate issue / PR though (feel free to open an issue). I am a bit scared that such a change might break existing instance. |
regulartim
left a comment
There was a problem hiding this comment.
Looks very good already! Just some minor healthcheck related details left.
|
Also, there seems to be a bug! When I try to run the container, I get: The reason might be that the file permissions of |
… into feature/migrate-uwsgi-to-gunicorn
…optimize healthcheck - removed redundant HEALTHCHECK from Dockerfile since default.yml healthcheck already overrides it when running via Docker Compose - added missing executable bit (+x) to entrypoint_gunicorn.sh - optimized healthcheck by adding a separate HTTP bind on port 8002 so healthcheck requests bypass the uWSGI queue on port 8001 and use curl -f http://localhost:8002 instead of Python TCP socket
|
Hey @regulartim! I have implemented all the changes and suggestions you mentioned:
Could you please review and check if everything is running fine? 🙏 |
|
Hey @SupRaKoshti ! The application does not even start. How are you testing your changes? |
… into feature/migrate-uwsgi-to-gunicorn
|
Hey @regulartim , I apologize for that! I realized my mistake — I was testing with The root cause of the bug is that I am now testing properly with
Will keep you updated as I progress. Sorry for the inconvenience! |
|
Hi @regulartim , Update: After replacing the healthcheck with
Option C fully resolves all the errors — no more My question: is the uWSGI protocol a hard requirement here? If not, I'd like to go with Option C (plain HTTP) as it eliminates all the protocol complexity. Happy to keep uWSGI if there's a specific reason for it! |
|
Hey @SupRaKoshti ! :) UWSGI has some performance benefits over http. Therefore I would prefer to use it. The unix socket approach (option B) sounds smart. That would also have a positive effect on performance, I guess. |
… into feature/migrate-uwsgi-to-gunicorn
- bind gunicorn to two UNIX sockets instead of TCP port 8001: - unix:/run/gunicorn-main.sock for all application traffic - unix:/run/gunicorn-health.sock for health checks only (separate health socket avoids queuing behind busy workers, see benoitc/gunicorn#1417) - add shared `gunicorn_sockets` volume mounted at /run in both app and nginx containers so both can access the socket files - replace app healthcheck with `test -S /run/gunicorn-health.sock` since there is no longer a TCP port to probe - update nginx upstreams in http.conf and https.conf: - django_main -> unix:/run/gunicorn-main.sock - django_health -> unix:/run/gunicorn-health.sock - move /hc location from locations.conf into http/https.conf and route it to django_health upstream fixes ForbiddenUWSGIRequest errors that occurred when gunicorn rejected TCP connections from nginx's Docker network IP (172.20.0.x), since UNIX socket connections bypass the uWSGI IP allowlist entirely
without --preload, each gunicorn worker independently loads the app and calls get_random_secret_key(), resulting in every worker having a different SECRET_KEY. when a login request is handled by worker A and the next request is handled by worker B, the SECRET_KEY mismatch causes Django to flush the session and redirect back to login. with --preload, the app is loaded once in the master process before forking workers, so all workers inherit the same SECRET_KEY via fork().
|
Hi @regulartim , Ready for review! here's a full summary of what i have done. What was changed:
Why UNIX sockets: Why Tested with |
regulartim
left a comment
There was a problem hiding this comment.
Nice, thanks! I tested it and have some changes that I will push later. You can consider your work done, good job!
Hi @regulartim , Thanks! Glad to hear it's working well!
I’ll do some more research on the non-root users topic, and if it looks like a worthwhile improvement, I’ll open a separate issue for it then we can discuss about it! |
Cool, thank you! |

Description
Migrated the server gateway interface from uWSGI (which is now in maintenance-only mode) to Gunicorn. Gunicorn is a well-established WSGI server and a drop-in replacement for uWSGI for standard Django applications. The server runs on
0.0.0.0:8001with 16 workers, matching the previous uWSGI configuration.Changes made:
Python Requirements
uwsgianduwsgitopwithgunicorn==25.1.0inrequirements/project-requirements.txtDocker
docker/entrypoint_gunicorn.sh(replacingdocker/entrypoint_uwsgi.sh) with the gunicorn server commanddocker/Dockerfileto remove uWSGI log directory and related commentsdocker/default.yml— renameduwsgiservice togunicorn, updated container name, entrypoint, command, and removed uWSGI-specific volume mount and stats portdocker/local.override.yml— renameduwsgiservice togunicorndocker/stag.override.yml— renameduwsgiservice togunicorndocker/version.override.yml— renameduwsgiservice togunicorndocker/elasticsearch.yml— renameduwsgiservice togunicornNginx Configuration
configuration/nginx/https.conf— replaceduwsgi_pass/uwsgi_cache/uwsgi_paramswithproxy_pass/proxy_cacheequivalents (protocol change from uWSGI binary to HTTP)configuration/nginx/http.conf— same uwsgi→proxy conversion ashttps.confconfiguration/nginx/django_server.conf— updated upstream hostname and commentManagement Script
gbctl— renamed alluwsgicontainer references togunicorn(affectscheck_downgrade,cmd_logs,cmd_health, andcmd_create_adminfunctions)Deleted Files
docker/entrypoint_uwsgi.shconfiguration/uwsgi/greedybear.iniRelated issues
Closes #891
Type of change
Checklist
Formalities
<feature name>. Closes #999develop.develop.Docs and tests
GUI changes
N/A - No GUI changes were made.