Схема: код в /srv/app, зависимости в виртуальном окружении, gunicorn слушает Unix-сокет, systemd следит за процессом, Nginx принимает запросы из интернета.
Окружение
$ apt update$ apt install -y python3-venv python3-dev build-essential$ adduser --system --group --home /srv/app app$ cd /srv/app$ sudo -u app python3 -m venv .venv$ sudo -u app .venv/bin/pip install --upgrade pip$ sudo -u app .venv/bin/pip install -r requirements.txt gunicornПробный запуск
Точка входа зависит от фреймворка: для Django — проект.wsgi:application, для Flask — модуль:app, для FastAPI — модуль:app с воркером uvicorn.
$ sudo -u app .venv/bin/gunicorn --bind 127.0.0.1:8000 myproject.wsgi:application$ # FastAPI:$ # sudo -u app .venv/bin/pip install "uvicorn[standard]"$ # sudo -u app .venv/bin/gunicorn -k uvicorn.workers.UvicornWorker --bind 127.0.0.1:8000 main:appСлужба systemd
[Unit]Description=My Python app (gunicorn)After=network.target[Service]User=appGroup=www-dataWorkingDirectory=/srv/appEnvironmentFile=/srv/app/.envRuntimeDirectory=appExecStart=/srv/app/.venv/bin/gunicorn --workers 3 --bind unix:/run/app/gunicorn.sock myproject.wsgi:applicationExecReload=/bin/kill -s HUP $MAINPIDRestart=on-failure[Install]WantedBy=multi-user.targetRuntimeDirectory создаёт /run/app при старте службы. Группа www-data нужна, чтобы Nginx мог подключиться к сокету. Число воркеров для начала — 2 × число ядер + 1, дальше подбирайте по нагрузке и памяти.
$ chmod 600 /srv/app/.env$ systemctl daemon-reload$ systemctl enable --now app$ systemctl status app --no-pagerNginx
server { listen 80; server_name example.com; location /static/ { alias /srv/app/static/; } location / { proxy_pass http://unix:/run/app/gunicorn.sock; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}$ ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/$ nginx -t && systemctl reload nginx$ certbot --nginx -d example.comОбновление кода
$ cd /srv/app$ sudo -u app git pull$ sudo -u app .venv/bin/pip install -r requirements.txt$ systemctl reload appreload отправляет gunicorn сигнал HUP: воркеры перезапускаются по очереди, соединения не обрываются.
Частые ошибки
- 502 и «Permission denied» к сокету в журнале Nginx — у службы не та группа или сокет создаётся не там, куда смотрит proxy_pass.
- Служба падает сразу после старта — смотрите
journalctl -u app -n 50: обычно ошибка импорта или не найден .env. - Статика Django не отдаётся — не выполнен collectstatic или STATIC_ROOT не совпадает с alias в Nginx.