Skip to Content

Chapitre 10.5 — Python & le pattern générique

⏱️ TL;DR — Python en prod (Django, FastAPI, Flask) suit exactement le même patron que Node : un process qui écoute en local (gunicorn pour du WSGI, uvicorn pour de l’ASGI), lancé par un service systemd, dans un virtualenv, avec Nginx en façade qui proxy_pass vers un port local ou un socket Unix. Et c’est là qu’on referme la partie : ce patron n’est pas propre à Python. Go compile un binaire qui écoute → même chose. Ruby lance puma → même chose. Rust, Java, Elixir → même chose. Toute app web se ramène à « un process local + un reverse proxy ». La seule vraie exception, c’est PHP (FastCGI au lieu de HTTP) et le statique (aucun process). Morale du cours : apprends le pattern, pas 15 recettes. Ce chapitre le formule noir sur blanc et en donne le tableau récapitulatif.

🎯 Objectifs

  • Déployer une app Python (WSGI/ASGI) avec gunicorn/uvicorn en service systemd derrière Nginx.
  • Comprendre virtualenv, le choix port local vs socket Unix, et le proxy_pass.
  • Situer Django, FastAPI, Flask dans ce schéma.
  • Généraliser le patron à Go, Ruby, Rust, Java… et savoir le reconnaître partout.
  • Retenir le tableau des stacks : ce qui change, ce qui ne change jamais.

Python : le même patron que Node

Une app Python ne se sert pas « toute seule » en prod. Le serveur de dev (python manage.py runserver, flask run, uvicorn --reload) est fait pour développer, pas pour encaisser du trafic. En prod, on met un serveur d’application devant le code — c’est le rôle de gunicorn ou uvicorn — et Nginx devant lui.

Deux mondes selon que ton framework est synchrone ou asynchrone :

  • WSGI (l’interface synchrone historique) : Django, Flask. Serveur d’app : gunicorn.
  • ASGI (l’interface asynchrone moderne, WebSockets, async/await) : FastAPI, Django en mode async, Starlette. Serveur d’app : uvicorn (souvent piloté par gunicorn avec des workers uvicorn).

Tu reconnais le schéma du cycle de vie d’une requête et de la Partie 9 : Nginx en façade, app en local. Seuls les noms changent.

1. Le virtualenv : isoler les dépendances

Python installe les dépendances par projet dans un environnement virtuel (virtualenv) : un dossier qui contient son interpréteur et ses paquets, sans polluer le Python système ni les autres projets. C’est l’équivalent du node_modules local.

cd /var/www/api-python # Crée un virtualenv dans le dossier .venv python3 -m venv .venv # L'active (le shell utilise désormais ce Python et ce pip) source .venv/bin/activate # Installe les dépendances du projet + le serveur d'app pip install -r requirements.txt gunicorn uvicorn

⚠️ Piège — Installer les paquets en global (sudo pip install ...) « pour aller plus vite ». Tu mélanges alors les dépendances de tous les projets et du système, et une mise à jour en casse un autre. Un virtualenv par app, toujours. Le service systemd pointera l’interpréteur du virtualenv (.venv/bin/gunicorn), pas le Python système.

2. Lancer gunicorn/uvicorn sur un socket local

On fait écouter le serveur d’app en local uniquement — soit un socket Unix (recommandé, comme PHP-FPM), soit un port sur 127.0.0.1. Jamais sur 0.0.0.0 : seul Nginx doit lui parler.

# WSGI (Django/Flask) via gunicorn, sur un socket Unix gunicorn --workers 3 --bind unix:/run/api-python.sock monprojet.wsgi:application # ASGI (FastAPI) via gunicorn pilotant des workers uvicorn gunicorn --workers 3 --worker-class uvicorn.workers.UvicornWorker \ --bind unix:/run/api-python.sock main:app
  • --workers 3 : nombre de process qui traitent les requêtes en parallèle (règle de départ courante : 2 × cœurs + 1, à ajuster selon la RAM — même logique que pm.max_children en 10.1).
  • --bind unix:/run/api-python.sock : écoute sur un socket Unix local. On aurait pu écrire --bind 127.0.0.1:8000 pour un port local.
  • monprojet.wsgi:application / main:app : trouver l’application (module : objet).

3. Le service systemd

Comme pour Node, on ne lance jamais gunicorn à la main dans un terminal : il faut qu’il redémarre au boot et après un crash. C’est le rôle de systemd (Partie 3). On écrit un unit file.

# /etc/systemd/system/api-python.service [Unit] Description=API Python (gunicorn) pour FormaCampus After=network.target [Service] User=www-data Group=www-data WorkingDirectory=/var/www/api-python # On appelle le gunicorn DU virtualenv, pas le système ExecStart=/var/www/api-python/.venv/bin/gunicorn \ --workers 3 --bind unix:/run/api-python.sock monprojet.wsgi:application Restart=always # Variables d'environnement (ou EnvironmentFile=/var/www/api-python/.env) Environment=DJANGO_SETTINGS_MODULE=monprojet.settings.prod [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload # recharge les units après édition sudo systemctl enable --now api-python # démarre ET active au boot sudo systemctl status api-python # vérifie que ça tourne journalctl -u api-python -f # suit les logs en direct

💡 Réflexeenable --now fait d’un coup enable (démarrage au boot) et start (démarrage immédiat). L’oubli classique est de faire start sans enable : ça marche… jusqu’au premier reboot, où le service ne remonte pas. enable est ce qui te sauve après un redémarrage du VPS.

4. Nginx devant : proxy_pass

Le server block est le même que pour Node : Nginx reçoit le trafic public et le relaie vers le socket local du serveur d’app.

server { listen 80; server_name api-py.formacampus.fr; location / { # Relaie vers le socket Unix de gunicorn proxy_pass http://unix:/run/api-python.sock; 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; } # Django/Flask ne servent pas le statique en prod : Nginx s'en charge location /static/ { alias /var/www/api-python/staticfiles/; expires 30d; } }

Les en-têtes X-Forwarded-* disent à l’app d’où vient vraiment la requête (l’IP du client, le protocole HTTPS), puisqu’elle ne voit directement que Nginx. Le bloc location /static/ rappelle un point important : le serveur d’app Python ne sert pas les fichiers statiques en prod (Django les rassemble via collectstatic), c’est Nginx qui les sert — exactement l’idée du chapitre 10.4.

🔒 Sécurité — Le serveur d’app écoute sur un socket Unix (ou 127.0.0.1), jamais sur 0.0.0.0. Ainsi il est injoignable directement depuis Internet : on ne peut l’atteindre qu’à travers Nginx, qui filtre, chiffre (TLS) et applique ses règles. Un gunicorn bindé sur 0.0.0.0:8000 avec le pare-feu mal réglé, c’est ton app exposée nue, sans HTTPS ni filtrage. Le socket local est aussi une mesure de sécurité, pas qu’une histoire de perf.

5. Django, FastAPI, Flask : les spécificités

Le patron est identique ; seuls quelques gestes diffèrent :

  • Django : python manage.py collectstatic (rassemble le statique pour Nginx) et python manage.py migrate (migrations, comme Doctrine en 10.2) au déploiement ; DEBUG = False et ALLOWED_HOSTS bien réglés en prod (l’équivalent du APP_DEBUG=0 de Symfony). Servi par gunicorn (projet.wsgi).
  • FastAPI : ASGI, servi par uvicorn (souvent via gunicorn + workers uvicorn) ; idéal pour l’async et les WebSockets.
  • Flask : WSGI minimaliste, servi par gunicorn (app:app). Le serveur intégré de Flask est explicitement marqué « pas pour la prod ».

⚠️ Piège — Laisser DEBUG = True en prod côté Django (ou lancer runserver/flask run comme serveur de prod). C’est le pendant exact du APP_DEBUG=1 de Symfony (10.2) : pages d’erreur qui exposent le code et la config, et serveur de dev monothread incapable de tenir la charge. En prod : DEBUG = False, un vrai serveur d’app (gunicorn/uvicorn) en service, Nginx devant.

Le pattern générique : ce qui ne change jamais

Recule d’un pas. Depuis la Partie 7, tout ce qu’on déploie tient dans une seule phrase :

Une app web en prod = un process qui écoute en local + Nginx en façade qui relaie.

Ce qui change d’une techno à l’autre est mineur : le nom du serveur d’app (Node lui-même, gunicorn, uvicorn, puma…) et le protocole entre Nginx et l’app (HTTP proxifié, ou FastCGI pour PHP). Ce qui ne change jamais : Nginx devant, l’app en local (socket ou port privé), un service systemd pour la maintenir en vie, la base non exposée, HTTPS via Let’s Encrypt.

C’est pour ça qu’apprendre à déployer Go, Ruby ou Rust ne demandera pas un nouveau cours :

  • Go : go build produit un binaire qui écoute sur un port. Service systemd + proxy_pass. Même patron.
  • Ruby on Rails : serveur d’app puma sur un socket. Service systemd + proxy_pass. Même patron.
  • Rust (Axum, Actix) : un binaire qui écoute. Même patron.
  • Java (Spring Boot) : un .jar qui écoute sur un port. Même patron.

Le tableau à retenir

StackCe qui tourne en localNginx lui parle en…Service systemd ?
Node / Next.jsnode (l’app elle-même)HTTP (proxy_pass)Oui
PHP / Symfony / WordPressPHP-FPMFastCGI (fastcgi_pass)Oui (FPM)
Python (Django/FastAPI/Flask)gunicorn / uvicornHTTP (proxy_pass)Oui
Go / Rust / Javale binaire / le .jarHTTP (proxy_pass)Oui
Ruby on RailspumaHTTP (proxy_pass)Oui
Statique / SPArienNginx sert les fichiersNon

Deux familles, en réalité : « ça parle HTTP en local » (presque tout : proxy_pass), « c’est PHP » (FastCGI vers FPM), et le cas béni du statique (Nginx seul). Voilà tout le paysage.

💡 Réflexe — Face à une stack que tu ne connais pas, ne cherche pas « le tuto déploiement de X ». Pose trois questions : (1) Comment cette techno écoute-t-elle en local (quel serveur d’app, quel port/socket) ? (2) Comment la garder en vie (service systemd) ? (3) Comment Nginx lui parle (proxy HTTP, ou statique) ? Tu as répondu au déploiement de n’importe quelle app web. C’est un pattern, pas un catalogue.

🧭 Sur FormaCampus — FormaCampus a beau être surtout Next.js + Symfony, son équipe data a un micro-service de recommandation en Python/FastAPI (ASGI, uvicorn) qui tourne sur le VPS comme service systemd, écoute sur /run/reco.sock, et est exposé par Nginx sous /api/reco/le même patron que l’API Symfony et le front Next, juste un serveur d’app différent. C’est la preuve par l’exemple du cours entier : une fois le patron « process local + Nginx » acquis, ajouter une brique dans n’importe quel langage ne coûte qu’un virtualenv, un unit systemd et un location de plus. FormaCampus tient front Next, API Symfony (FPM), blog WordPress, docs statiques et micro-service Python sur un seul VPS, avec un seul modèle mental.

📚 La doc — Références : la doc gunicorn (« Deploying Gunicorn », qui recommande précisément Nginx devant), uvicorn (« Deployment »), et les guides « Deployment » officiels de Django, FastAPI et Flask — tous décrivent ce même montage serveur d’app + reverse proxy. Pour systemd, man systemd.service.

✏️ Exercices

Exercice 1 — Le service Python. Écris le unit systemd minimal pour une API FastAPI dans /var/www/reco, servie par gunicorn avec des workers uvicorn, écoutant sur /run/reco.sock, sous l’utilisateur www-data, redémarrant après un crash.

✅ Solution

# /etc/systemd/system/reco.service [Unit] Description=API reco FastAPI (uvicorn) After=network.target [Service] User=www-data Group=www-data WorkingDirectory=/var/www/reco ExecStart=/var/www/reco/.venv/bin/gunicorn \ --workers 3 --worker-class uvicorn.workers.UvicornWorker \ --bind unix:/run/reco.sock main:app Restart=always [Install] WantedBy=multi-user.target

Puis sudo systemctl daemon-reload && sudo systemctl enable --now reco. Points clés : le gunicorn du virtualenv (.venv/bin/gunicorn), --worker-class uvicorn.workers.UvicornWorker (car ASGI), le socket Unix local, Restart=always, et enable pour le boot.

Exercice 2 — Traduis le patron. Un collègue doit déployer une API Go (un binaire qui écoute sur 127.0.0.1:8080) et te demande « comment on fait, il n’y a pas de tuto Go dans le cours ? ». Réponds en trois étapes concrètes.

✅ Solution

C’est le même patron, aucun tuto spécifique nécessaire. (1) Faire tourner : un service systemd dont ExecStart lance le binaire Go (qui écoute déjà sur 127.0.0.1:8080), avec Restart=always et enable pour le boot. (2) Nginx devant : un server block avec location / { proxy_pass http://127.0.0.1:8080; } et les en-têtes X-Forwarded-*. (3) HTTPS via Let’s Encrypt (Partie 8) et base non exposée. Go, Python, Node : même montage, seul le process local change.

Exercice 3 — Range les stacks. Classe ces stacks en deux colonnes : « Nginx parle HTTP (proxy_pass) » et « autre cas ». Node/Next, WordPress, FastAPI, un site Astro statique, Rails, Symfony.

✅ Solution

Nginx parle HTTP (proxy_pass) : Node/Next, FastAPI (gunicorn/uvicorn), Rails (puma). Autres cas : WordPress et Symfony → FastCGI vers PHP-FPM (fastcgi_pass) ; site Astro statique → aucun process, Nginx sert les fichiers directement. On retrouve les deux vraies familles : « parle HTTP en local » (la majorité), « c’est PHP » (FastCGI), plus le cas béni du statique.

🧠 Quiz de révision

1. Quels serveurs d’app utilise-t-on pour du WSGI et pour de l’ASGI en Python ?

WSGI (synchrone : Django, Flask) → gunicorn. ASGI (asynchrone : FastAPI, Django async) → uvicorn, souvent piloté par gunicorn avec des workers uvicorn. Dans les deux cas, Nginx est en façade et proxy_pass vers le socket local.

2. Pourquoi un virtualenv, et que doit viser l’ExecStart du service systemd ?

Le virtualenv isole les dépendances par projet (son interpréteur, ses paquets), sans polluer le Python système ni les autres apps. Le service systemd doit viser le binaire du virtualenv (.venv/bin/gunicorn), pas le Python global.

3. Formule en une phrase le pattern générique de déploiement du cours.

Une app web en prod = un process qui écoute en local + Nginx en façade qui relaie (via proxy_pass HTTP dans la plupart des cas, via FastCGI pour PHP), le tout maintenu en vie par systemd, base non exposée, HTTPS devant.

4. Qu’est-ce qui change, et qu’est-ce qui ne change jamais, entre déployer du Python, du Go et du Node ?

Ce qui change : le nom du serveur d’app (node, gunicorn/uvicorn, le binaire Go) et le port/socket local. Ce qui ne change jamais : Nginx en façade, l’app en local (socket ou 127.0.0.1), un service systemd, la base non exposée, HTTPS via Let’s Encrypt. Même patron partout (sauf PHP en FastCGI et le statique sans process).

5. Pourquoi Django/Flask ne servent-ils pas leurs fichiers statiques en prod ?

Parce que c’est le travail de Nginx, bien plus efficace pour ça (chapitre 10.4). En prod, Django rassemble le statique avec collectstatic dans un dossier, et Nginx le sert via un location /static/. Le serveur d’app Python ne traite que le dynamique.


Fin de la Partie 10. Tu as vu que le patron « app derrière Nginx » se décline à toutes les stacks — PHP en FastCGI, Python/Node/Go en proxy, statique sans process. Il reste à donner à ces apps où stocker leurs données : Partie 11 — Bases de données en prod.

Last updated on