Template
Python Production Stack
Python, Nginx, Gunicorn and a hardened systemd service for one FastAPI, Django or Flask app.
Steps, in order · version 1.0
Supported systems
Ubuntu 22.04+, Debian 12+, RHEL-compatible 8+ (AlmaLinux, Rocky Linux, RHEL, CentOS Stream, Oracle Linux), Amazon Linux 2023+
Options
As they appear on the run form, with their defaults.
Application name
Text
Lowercase letters, digits and hyphens, such as api. Names the folder, account and service.
Default: Required
Application module
Text
What Gunicorn loads, such as config.wsgi:application for Django or app.main:app for FastAPI.
Default: Required
Application interface
One choice
- ASGI: FastAPI, Starlette, Django over ASGI
- WSGI: Django, Flask
Default: ASGI: FastAPI, Starlette, Django over ASGI
Python
One choice
On Ubuntu and Debian, both choices install the release's one Python 3.
- Newest Python 3 in the distribution's repositories
- The distribution's default python3
Default: Newest Python 3 in the distribution's repositories
Port on 127.0.0.1
Text
Gunicorn listens here, on this server only.
Default: 8000
Python tools
Any of the choices
- uv
- Poetry
- Pipenv
Default: uv
Client headers
Any of the choices
For database drivers and libraries that compile against them.
- PostgreSQL (psycopg)
- MySQL and MariaDB (mysqlclient)
- libxml2 and libxslt (lxml)
- JPEG and zlib (Pillow)
Default: None
What you get
Everything one Python web application needs on the server except its code: Python with venv and pip, Nginx, Gunicorn in the application's own virtual environment (with Uvicorn workers for ASGI), the Python tools you choose with a compiler and build headers, a service account, Gunicorn settings and a hardened systemd service on 127.0.0.1. The service is installed but not started until you deploy.
Choosing the options
Application name: lowercase letters, digits and hyphens, such as api. It names /srv/api, the api account and the api service.
Application module: what Gunicorn loads, such as app.main:app for a FastAPI app in app/main.py; config.asgi:application or config.wsgi:application for Django, where config holds settings.py; app:app for Flask in app.py.
Application interface: ASGI for FastAPI, Starlette and Django over ASGI; WSGI for Django and Flask.
Python: Newest Python 3 on the RHEL family installs the newest Python packaged beside the system one, such as 3.12; on Ubuntu and Debian both choices install the release's Python 3.
Port on 127.0.0.1: where Gunicorn listens, on this server only. 8000 unless something else uses it.
Python tools: uv, Poetry or Pipenv, installed with pipx and available to every account.
Client headers: for dependencies that compile against them, such as PostgreSQL for psycopg, MySQL and MariaDB for mysqlclient, libxml2 for lxml, and JPEG and zlib for Pillow.
What happens
- Update Packages.
- Install Python.
- Install Nginx.
- Install Gunicorn into the application's own virtual environment,
/srv/api/venvforapi. - Install Python Tools, with a compiler and build headers.
- Configure Python App: the account, the Gunicorn settings (
/etc/api/gunicorn.conf.pyforapi) and the service.
After the run
For an application named api on port 8000:
- Deploy the code to
/srv/api/current. - Install its requirements:
/srv/api/venv/bin/pip install -r /srv/api/current/requirements.txt, or with the tool you chose, for uvuv pip install --python /srv/api/venv/bin/python -r requirements.txt. - Put its settings in
/etc/api/environment, owned by root, mode 0640, groupapi. - Start it:
systemctl enable --now api, and checkcurl http://127.0.0.1:8000/. - Put HTTPS in front with Configure Reverse Proxy or the Nginx Reverse Proxy + Auto-Renewing SSL template, forwarding to
http://127.0.0.1:8000.
Common problems
- The port is already in use, or the name is taken by an existing account or service. See Configure Python App.
- The Uvicorn worker does not import: see Install Gunicorn.
- A header package is not available on the RHEL family: enable CodeReady Builder; see Install Python Tools.
- The service starts and stops once you enable it: read
journalctl -u api -n 50; the module path and missing settings are the usual causes.