Déployer une stack Flask PostgreSQL Nginx est la voie standard pour faire passer une application web Python de votre ordinateur portable à un serveur de production opérationnel. Ce guide parcourt chaque couche : une base de données PostgreSQL, Gunicorn comme serveur d’application WSGI, systemd pour maintenir le processus en vie, et Nginx comme reverse proxy exposé à internet. À la fin, vous disposerez d’une configuration robuste et prête pour la production sur laquelle vous pourrez vous appuyer.
Prérequis
- Serveur Ubuntu 22.04 ou 24.04 (accès root ou sudo)
- Un nom de domaine pointant vers l’IP du serveur (optionnel pour le SSL plus tard)
- Familiarité de base avec la ligne de commande Linux et Python
- Une application Flask prête à déployer (ou utilisez l’exemple minimal de ce guide)
Installation des dépendances système
Commencez par rafraîchir l’index des paquets et installez tout ce dont vous avez besoin en une seule passe :
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv \
postgresql postgresql-contrib \
nginx curl
Vérifiez les installations :
python3 --version # Python 3.10+
psql --version # psql 14+
nginx -v # nginx/1.22+
Configuration de PostgreSQL
Créer une base de données et un utilisateur
Passez au compte système postgres et ouvrez le shell interactif :
sudo -u postgres psql
Dans psql, créez un utilisateur et une base de données dédiés. N’utilisez jamais le superutilisateur postgres par défaut pour les connexions applicatives :
CREATE USER flaskapp WITH PASSWORD 'StrongPassw0rd!';
CREATE DATABASE flaskdb OWNER flaskapp;
GRANT ALL PRIVILEGES ON DATABASE flaskdb TO flaskapp;
\q
Tester la connexion
Confirmez la connectivité avant de l’intégrer à Flask :
psql -U flaskapp -h 127.0.0.1 -d flaskdb -c "\conninfo"
Vous devriez voir la confirmation que la connexion a réussi. En cas d’échec, vérifiez que pg_hba.conf autorise l’authentification md5 ou scram-sha-256 pour les connexions locales :
sudo nano /etc/postgresql/14/main/pg_hba.conf
# Vérifiez que cette ligne existe :
# host all all 127.0.0.1/32 scram-sha-256
sudo systemctl restart postgresql
Déploiement de l’application Flask
Structure du projet
Placez votre application dans /srv/flaskapp. Utiliser /srv sépare les applications web des fichiers système :
/srv/flaskapp/
├── venv/
├── app/
│ ├── __init__.py
│ └── models.py
├── wsgi.py
├── .env
└── requirements.txt
Créer l’environnement virtuel
sudo mkdir -p /srv/flaskapp
sudo chown $USER:$USER /srv/flaskapp
cd /srv/flaskapp
python3 -m venv venv
source venv/bin/activate
Installer les paquets Python
pip install flask flask-sqlalchemy psycopg2-binary gunicorn python-dotenv
pip freeze > requirements.txt
Application Flask minimale
Créez app/__init__.py :
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"]
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
db.init_app(app)
@app.route("/")
def index():
return "Flask + PostgreSQL + Nginx is running."
return app
Créez wsgi.py (le point d’entrée appelé par Gunicorn) :
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run()
Variables d’environnement
Stockez les secrets dans /srv/flaskapp/.env. Ne commitez jamais ce fichier dans le contrôle de version :
DATABASE_URL=postgresql://flaskapp:StrongPassw0rd!@127.0.0.1:5432/flaskdb
SECRET_KEY=replace-with-a-long-random-string
FLASK_ENV=production
Restreignez immédiatement les permissions :
chmod 600 /srv/flaskapp/.env
Tester Gunicorn manuellement
Avant de configurer systemd, vérifiez que Gunicorn peut démarrer votre application :
source /srv/flaskapp/venv/bin/activate
cd /srv/flaskapp
gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi:app
Visitez http://your-server-ip:8000 — vous devriez voir la réponse Flask. Si c’est le cas, arrêtez Gunicorn avec Ctrl+C et passez à systemd.
Service systemd pour Gunicorn
Exécuter Gunicorn sous systemd vous offre des redémarrages automatiques en cas de plantage, un démarrage au boot et une journalisation centralisée via journalctl.
Choisir le nombre de workers
| RAM du serveur | Workers recommandés |
|---|---|
| 512 Mo | 2 |
| 1 Go | 3 |
| 2 Go | 5 |
| 4 Go+ | 9 |
Une formule courante : (2 × nombre de cœurs CPU) + 1. Trois workers est une valeur par défaut sûre pour la plupart des petites instances VPS.
Créer le fichier unit du service
sudo nano /etc/systemd/system/flaskapp.service
Collez ce qui suit — ajustez les chemins et l’utilisateur selon vos besoins :
[Unit]
Description=Gunicorn instance for Flask app
After=network.target postgresql.service
[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/flaskapp
EnvironmentFile=/srv/flaskapp/.env
ExecStart=/srv/flaskapp/venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/flaskapp/gunicorn.sock \
--access-logfile /var/log/flaskapp/access.log \
--error-logfile /var/log/flaskapp/error.log \
wsgi:app
RuntimeDirectory=flaskapp
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
Explication des champs clés :
EnvironmentFile— charge vos secrets.envdans l’environnement du processusRuntimeDirectory— systemd crée automatiquement/run/flaskapp/et le nettoie à l’arrêtAfter=postgresql.service— garantit que la base de données est démarrée avant l’application- socket
unix:— plus rapide que TCP pour la communication locale Nginx-vers-Gunicorn
Créez le répertoire de logs et ajustez la propriété :
sudo mkdir -p /var/log/flaskapp
sudo chown www-data:www-data /var/log/flaskapp
sudo chown -R www-data:www-data /srv/flaskapp
Activez et démarrez le service :
sudo systemctl daemon-reload
sudo systemctl enable flaskapp
sudo systemctl start flaskapp
sudo systemctl status flaskapp
Vous devriez voir Active: active (running). Vérifiez que le socket a bien été créé :
ls -la /run/flaskapp/gunicorn.sock
Configuration de Nginx comme reverse proxy
Nginx se place devant Gunicorn, gère le trafic HTTP/HTTPS entrant et transmet les requêtes applicatives au socket Unix.
Créer la configuration du site
sudo nano /etc/nginx/sites-available/flaskapp
server {
listen 80;
server_name example.com www.example.com;
# Fichiers statiques servis directement par Nginx — sans overhead Gunicorn
location /static/ {
alias /srv/flaskapp/app/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://unix:/run/flaskapp/gunicorn.sock;
proxy_http_version 1.1;
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;
# Évite que les clients lents monopolisent les workers Gunicorn
proxy_read_timeout 90;
proxy_connect_timeout 90;
}
}
Activez le site et testez la configuration :
sudo ln -s /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Ajouter le SSL avec Certbot
En production, terminez toujours le TLS au niveau de Nginx. Certbot automatise l’émission et le renouvellement des certificats :
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot réécrit votre configuration Nginx avec les directives SSL et configure le renouvellement automatique via un timer systemd.
Scénario concret
Vous avez une application SaaS en production construite avec Flask qui traite des commandes clients stockées dans PostgreSQL. Le trafic culmine à midi avec environ 200 utilisateurs simultanés. Sans le tamponnage de Nginx, les clients mobiles lents maintiendraient les workers Gunicorn ouverts pendant plus de 30 secondes chacun, provoquant des timeouts de requêtes. Avec la configuration de ce guide :
- Nginx accepte la connexion immédiatement et tamponne le client lent
- Il transmet le corps complet de la requête à Gunicorn en quelques millisecondes
- Gunicorn traite la requête et renvoie la réponse à Nginx
- Nginx diffuse la réponse au client lent pendant que Gunicorn est déjà libre pour la prochaine requête
Vos trois workers Gunicorn peuvent désormais servir bien plus de trois utilisateurs simultanés. Ajoutez davantage de workers ou basculez vers les workers asynchrones gevent lorsque vous avez besoin d’une plus grande montée en charge.
Pièges et Cas Particuliers
Permissions du socket Unix : L’utilisateur www-data de Nginx doit pouvoir lire le socket Gunicorn. Faire tourner les deux sous www-data (comme indiqué ci-dessus) est la solution la plus simple. Si ils tournent sous des utilisateurs différents, ajoutez www-data au groupe de l’application et définissez le socket à 660.
DATABASE_URL avec des caractères spéciaux : Les mots de passe contenant @, / ou % doivent être encodés en pourcentage dans l’URL. Alternativement, utilisez des variables d’environnement individuelles (PGUSER, PGPASSWORD, etc.) et construisez l’URL en Python.
python-dotenv manquant en production : EnvironmentFile dans systemd lit le .env directement — vous n’avez pas besoin de python-dotenv pour le chemin systemd. Vous en avez besoin si vous exécutez flask run en local.
Patron factory vs. app au niveau module : wsgi:app de Gunicorn attend un callable. Avec le patron factory, exposez l’app créée au niveau module dans wsgi.py comme indiqué ci-dessus. N’appelez pas create_app() à l’intérieur d’une fonction sans exposer le résultat.
max_connections de PostgreSQL : Chaque worker Gunicorn ouvre sa propre connexion à la base de données. Avec 9 workers sur 4 serveurs, vous pourriez atteindre la limite par défaut de 100 connexions. Utilisez PgBouncer comme pooler de connexions devant PostgreSQL pour les déploiements à grand nombre de workers.
Résolution de Problèmes
502 Bad Gateway de Nginx :
Vérifiez que le service Gunicorn tourne et que le socket existe :
sudo systemctl status flaskapp
ls /run/flaskapp/gunicorn.sock
Consultez également les logs d’erreur Nginx : sudo tail -50 /var/log/nginx/error.log
[CRITICAL] WORKER TIMEOUT dans les logs Gunicorn :
Une requête prend plus longtemps que le timeout par défaut de 30 secondes. Augmentez avec --timeout 120 ou optimisez la requête lente qui le déclenche.
OperationalError: could not connect to server :
PostgreSQL ne tourne pas ou le DATABASE_URL est incorrect. Testez la chaîne de connexion directement avec psql $DATABASE_URL.
Permission denied sur le socket :
Inadéquation d’utilisateur entre Nginx et Gunicorn. Vérifiez que les deux tournent sous www-data :
ps aux | grep -E "gunicorn|nginx" | awk '{print $1, $11}'
Fichiers statiques renvoyant 404 :
Le chemin alias dans Nginx doit correspondre à votre répertoire statique réel. Note : alias requiert un slash final ; root non.
Résumé
- Installez PostgreSQL, créez un utilisateur et une base de données dédiés, et restreignez les permissions du fichier
.envpour sécuriser les identifiants - Exécutez Flask sous Gunicorn avec un socket Unix — n’exposez jamais Gunicorn directement à internet
- Utilisez un unit systemd
[Service]avecEnvironmentFileetRuntimeDirectorypour une gestion propre des processus et un redémarrage automatique - Configurez Nginx pour proxifier
/vers le socket Gunicorn et servir/static/directement afin d’éliminer la charge inutile sur le serveur applicatif - Ajoutez le SSL via Certbot immédiatement — le HTTP simple n’est jamais acceptable en production
- Surveillez l’épuisement du pool de connexions avec PostgreSQL lors du passage à l’échelle des workers Gunicorn ; utilisez PgBouncer si nécessaire