Nasazení Python aplikace na VPS: systemd, Gunicorn, Uvicorn a Nginx
Kompletní postup nasazení Flask, Django nebo FastAPI aplikace na Linux server – virtuální prostředí, Gunicorn s Uvicorn workery, služba systemd, Nginx a HTTPS.
python app.py v otevřeném SSH sezení není nasazení. Po odhlášení aplikace spadne, po restartu serveru se nespustí a vývojový server není stavěný na produkční provoz. Tento návod ukazuje osvědčenou sestavu: aplikační server Gunicorn (nebo Uvicorn), správa procesu přes systemd a Nginx jako reverzní proxy s HTTPS. Příkazy jsou pro Debian 12 / Ubuntu 22.04+.
Architektura
Internet → Nginx (:443, TLS, statické soubory) → Gunicorn (127.0.0.1:8000) → vaše aplikace
↑ spravuje systemd
1. Uživatel a adresáře
Aplikace nemá běžet pod rootem:
sudo apt update
sudo apt install python3-venv python3-pip nginx git
sudo adduser --system --group --home /srv/mojeapp mojeapp
sudo -u mojeapp git clone https://github.com/vy/mojeapp.git /srv/mojeapp/app
2. Virtuální prostředí a závislosti
cd /srv/mojeapp
sudo -u mojeapp python3 -m venv venv
sudo -u mojeapp venv/bin/pip install -r app/requirements.txt gunicorn
Pro FastAPI přidejte i uvicorn:
sudo -u mojeapp venv/bin/pip install "uvicorn[standard]"
3. Ruční test
Před psaním služby ověřte, že aplikace vůbec startuje:
cd /srv/mojeapp/app
# Flask (objekt app v souboru app.py):
sudo -u mojeapp ../venv/bin/gunicorn -b 127.0.0.1:8000 app:app
# Django (projekt "mysite"):
sudo -u mojeapp ../venv/bin/gunicorn -b 127.0.0.1:8000 mysite.wsgi:application
# FastAPI (objekt app v main.py):
sudo -u mojeapp ../venv/bin/gunicorn -b 127.0.0.1:8000 -k uvicorn.workers.UvicornWorker main:app
V druhém terminálu: curl -i http://127.0.0.1:8000/. Když odpoví, ukončete Ctrl+C.
Poznámka k FastAPI: novější verze Uvicornu (0.30+) mají vlastního správce procesů, takže lze použít i uvicorn main:app --workers 4. Varianta s Gunicornem je stále běžná a funguje.
4. Proměnné prostředí
Tajné hodnoty nepatří do gitu ani do unit souboru. Vytvořte /etc/mojeapp.env:
DATABASE_URL=postgresql://mojeapp:heslo@localhost/mojeapp
SECRET_KEY=dlouhy-nahodny-retezec
DJANGO_SETTINGS_MODULE=mysite.settings
sudo chown root:mojeapp /etc/mojeapp.env
sudo chmod 640 /etc/mojeapp.env
5. Služba systemd
/etc/systemd/system/mojeapp.service:
[Unit]
Description=Moje Python aplikace
After=network.target
[Service]
Type=notify
User=mojeapp
Group=mojeapp
WorkingDirectory=/srv/mojeapp/app
EnvironmentFile=/etc/mojeapp.env
ExecStart=/srv/mojeapp/venv/bin/gunicorn \
--workers 3 \
--bind 127.0.0.1:8000 \
--access-logfile - \
--timeout 60 \
app:app
ExecReload=/bin/kill -s HUP $MAINPID
Restart=always
RestartSec=3
# Základní zabezpečení
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
[Install]
WantedBy=multi-user.target
Type=notify– Gunicorn od verze 20 podporuje notifikaci systemd, takže systemd ví, kdy je aplikace skutečně připravená. Pokud používáte samotný Uvicorn, dejteType=simple.--workers– rozumný start je2 × počet jader + 1. U async workerů (Uvicorn) stačí méně, typicky počet jader.--access-logfile -– přístupový log jde na stdout, a tedy do journalu.ExecReloadsHUP– Gunicorn plynule restartuje workery bez výpadku.- Pro FastAPI přidejte do
ExecStartvolbu-k uvicorn.workers.UvicornWorkera cílmain:app.
Spuštění:
sudo systemctl daemon-reload
sudo systemctl enable --now mojeapp
systemctl status mojeapp
journalctl -u mojeapp -f
6. Nginx jako reverzní proxy
/etc/nginx/sites-available/mojeapp:
server {
listen 80;
server_name mojeapp.cz www.mojeapp.cz;
client_max_body_size 20M;
location /static/ {
alias /srv/mojeapp/app/static/;
expires 30d;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}
}
sudo ln -s /etc/nginx/sites-available/mojeapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
U Djanga nejdřív spusťte manage.py collectstatic, aby se statické soubory zkopírovaly do adresáře, na který ukazuje alias (nastavení STATIC_ROOT).
Pro websockety (např. FastAPI) přidejte do location /:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
7. HTTPS přes Let’s Encrypt
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d mojeapp.cz -d www.mojeapp.cz
Certbot upraví konfiguraci Nginxu a nastaví automatickou obnovu. Ověření: sudo certbot renew --dry-run.
Aplikace za proxy musí vědět, že původní požadavek byl HTTPS (kvůli generování URL a bezpečným cookies):
- Django:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")a nastavenýALLOWED_HOSTS. - Flask:
from werkzeug.middleware.proxy_fix import ProxyFix; app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1). - Gunicorn/Uvicorn: hlavičkám
X-Forwarded-*důvěřují ve výchozím stavu jen z127.0.0.1, což při proxy na stejném stroji stačí.
8. Firewall
sudo ufw allow OpenSSH
sudo ufw allow "Nginx Full"
sudo ufw enable
Port 8000 se ven neotevírá – Gunicorn poslouchá jen na 127.0.0.1.
9. Nasazení nové verze
cd /srv/mojeapp/app
sudo -u mojeapp git pull
sudo -u mojeapp ../venv/bin/pip install -r requirements.txt
# Django: sudo -u mojeapp ../venv/bin/python manage.py migrate
sudo systemctl reload mojeapp
Tyto kroky se vyplatí uložit do skriptu deploy.sh.
Řešení problémů
| Příznak | Kde hledat |
|---|---|
| 502 Bad Gateway | aplikace neběží: systemctl status mojeapp, journalctl -u mojeapp -n 100 |
| 504 Gateway Timeout | pomalý požadavek – zvyšte --timeout i proxy_read_timeout, nebo přesuňte práci na pozadí |
ModuleNotFoundError ve službě |
špatný WorkingDirectory nebo ExecStart neukazuje do venv |
| Chybí proměnné prostředí | práva k /etc/mojeapp.env, překlep v EnvironmentFile |
| Statické soubory 404 | cesta v alias musí končit lomítkem, Nginx potřebuje právo čtení |
Celý postup můžete projít i s ColdHost AI v režimu Server a DevOps: připojte VPS přes SSH, AI navrhne jednotlivé příkazy a vy každý schválíte kliknutím, takže vidíte, co se na serveru děje. Pro úpravy konfigurace se dá použít prohlížeč souborů přes SFTP.