Django en prod : 7 erreurs qui m'ont coûté des nuits
Trois pannes en un mois sur une application que je croyais bien déployée : DEBUG laissé à True, runserver en production, requêtes N+1, aucun monitoring. Les 7 erreurs Django qui m'ont coûté des nuits blanches, avec la configuration exacte qui les règle.
3h du matin, mon téléphone buzze. "Le site est down, les utilisateurs ne peuvent plus s'inscrire." C'était la troisième fois ce mois-là que Housing AI plantait en production. J'étais pourtant convaincu d'avoir tout bien fait avec Django. Spoiler : j'avais tout faux.
Après 2 ans et 15 APIs en production, j'ai enfin compris pourquoi mes applications Django tombaient comme des mouches. Voici les 7 erreurs qui m'ont coûté des nuits blanches, et surtout comment les éviter.
L'erreur qui tue : DEBUG=True en production
Ma première application déployée a planté spectaculairement le jour du lancement. J'avais laissé DEBUG=True dans mes settings de production. Résultat ? Pages d'erreur avec stack traces complets exposées aux utilisateurs, performances dégradées, et pire : toutes mes variables d'environnement affichées en clair.
Ce que j'aurais dû faire dès le début :
- Séparer les settings par environnement (
settings/dev.py,settings/prod.py) - Utiliser des variables d'environnement pour tous les secrets
DEBUG = Falseen production, TOUJOURS- Configurer
ALLOWED_HOSTScorrectement
# settings/prod.py
import os
from .base import *
DEBUG = False
ALLOWED_HOSTS = [os.getenv('DOMAIN'), 'www.monsite.com']
SECRET_KEY = os.getenv('SECRET_KEY')
# Logs détaillés sans DEBUG
LOGGING = {
'version': 1,
'handlers': {
'file': {
'class': 'logging.FileHandler',
'filename': 'django.log',
},
},
'root': {
'handlers': ['file'],
},
}
Cette erreur m'a appris une règle d'or : sécurité avant tout. Un secret exposé peut détruire ton business en une nuit.
Servir avec runserver = suicide professionnel
J'ai honte de l'avouer, mais j'ai déployé ma première app avec python manage.py runserver sur un VPS. L'application s'écroulait dès 10 utilisateurs connectés. Le serveur de développement Django n'est PAS fait pour la production.
Ma stack actuelle (qui tient la charge) :
- Gunicorn comme serveur WSGI
- Nginx comme proxy inverse et pour les fichiers statiques
- Systemd pour gérer les processus
Configuration Gunicorn qui marche :
gunicorn --workers=4 --worker-class=gthread --threads=2 --worker-connections=1000 myproject.wsgi:application
Nginx gère les fichiers statiques, Gunicorn s'occupe du Python. Cette séparation des responsabilités a divisé mes temps de réponse par 10.
D'ailleurs, j'explique en détail ma stack technique complète dans mon article sur comment j'ai construit Housing AI si tu veux voir l'architecture complète.
L'ORM Django : ami ou ennemi ?
L'ORM Django, c'est magique... jusqu'à ce que ça devienne ton pire cauchemar. J'ai eu des pages qui prenaient 15 secondes à charger à cause du problème N+1. Chaque article affiché déclenchait une requête pour récupérer l'auteur. 1000 articles = 1000 requêtes SQL.
Mes optimisations qui font vraiment la différence :
select_related() pour les ForeignKey :
# Lent - N+1 queries
articles = Article.objects.all()
for article in articles:
print(article.author.name) # Nouvelle requête à chaque fois
# Rapide - 1 seule requête
articles = Article.objects.select_related('author')
prefetch_related() pour les ManyToMany :
# Pour récupérer articles + tous leurs tags d'un coup
articles = Article.objects.prefetch_related('tags')
Index sur les colonnes les plus requêtées :
class Article(models.Model):
title = models.CharField(max_length=200, db_index=True)
created_at = models.DateTimeField(db_index=True)
Depuis que j'applique ces best practices, mes temps de réponse sont passés de 3 secondes à 200 ms. L'ORM Django est puissant, mais il faut savoir le dompter.
Fichiers statiques : le piège invisible
Mes CSS ne se chargeaient jamais. Mes images disparaissaient après chaque déploiement. Les fichiers statiques avec Django, c'est un enfer si tu ne comprends pas la logique.
Ma configuration bulletproof :
# settings/prod.py
STATIC_URL = '/static/'
STATIC_ROOT = '/var/www/monsite/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = '/var/www/monsite/media/'
# Pour servir depuis un CDN
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Process de déploiement :
python manage.py collectstatic --noinput- Nginx sert directement
/static/et/media/ - Django ne touche jamais aux fichiers statiques
J'utilise WhiteNoise pour les petites apps et AWS S3 avec CloudFront pour les gros projets comme Housing AI. Ça change la vie.
Monitoring : voir les problèmes avant tes users
"Pourquoi personne ne m'a dit que le site plantait ?" Cette phrase, je l'ai dite trop souvent. J'apprenais les crashes par les utilisateurs qui me contactaient sur Twitter. Pas professionnel du tout.
Mon setup de monitoring actuel :
Sentry pour les erreurs :
# settings/prod.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
sentry_sdk.init(
dsn="TON_DSN_SENTRY",
integrations=[DjangoIntegration()],
traces_sample_rate=0.1,
)
Logs structurés :
import logging
logger = logging.getLogger(__name__)
def ma_vue(request):
try:
# ton code
pass
except Exception as e:
logger.error(f"Erreur dans ma_vue: {e}", extra={
'user_id': request.user.id,
'path': request.path,
})
Maintenant, je sais en temps réel si quelque chose déconne. Sentry m'envoie un Slack, je peux debugger avec le contexte complet. Ça change tout.
Cache et performance : Redis to the rescue
Housing AI scrapait des milliers d'annonces de logement. Chaque page d'accueil déclenchait des calculs lourds. 5 secondes de chargement, c'était normal. Jusqu'à ce que je découvre Redis.
Ma stratégie de cache en 3 niveaux :
1. Cache de vues :
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 15 minutes
def liste_logements(request):
# Vue mise en cache
pass
2. Cache de queries :
from django.core.cache import cache
def get_logements_populaires():
key = 'logements_populaires'
result = cache.get(key)
if not result:
result = Logement.objects.filter(populaire=True)[:10]
cache.set(key, result, 60 * 30) # 30 minutes
return result
3. Cache de templates :
{% load cache %}
{% cache 500 sidebar request.user.id %}
<!-- Contenu lourd -->
{% endcache %}
Résultat : pages d'accueil passées de 5 secondes à 300 ms. Redis et Django, c'est magique quand c'est bien configuré.
Comme je le mentionnais dans mon article sur l'authentification JWT, la performance et la simplicité vont de pair. Pas besoin de sur-complexifier.
Base de données : migrations et sauvegardes
J'ai perdu une base de données complète une fois. Une migration foireuse, pas de backup, 3 mois de données utilisateurs parties en fumée. Depuis, je suis parano sur la sécurité des données.
Mon process de migrations bulletproof :
1. Toujours tester les migrations sur une copie de prod :
# Dump de la prod
pg_dump production_db > backup_before_migration.sql
# Test sur environnement de staging identique
python manage.py migrate --plan
python manage.py migrate
2. Migrations réversibles :
# Éviter les migrations destructives
def forwards():
# Supprimer une colonne directement
# Migrations en étapes
# Étape 1: Ajouter nouvelle colonne
# Étape 2: Migrer les données
# Étape 3: Supprimer ancienne colonne
3. Backups automatiques :
# Cron job quotidien
0 2 * * * pg_dump monsite_prod | gzip > /backups/db_$(date +\%Y\%m\%d).sql.gz
Une donnée perdue ne revient jamais. Mieux vaut être parano que de pleurer après.
Ma checklist de déploiement Django
Après tous ces fails, j'ai créé une checklist que je suis religieusement pour chaque déploiement Django en production. Ça m'a sauvé la peau plus d'une fois :
Avant le déploiement :
DEBUG = False- Variables d'environnement pour tous les secrets
ALLOWED_HOSTSconfiguré- Tests qui passent
- Backup de la base de données
Configuration serveur :
- Gunicorn et Nginx
- Certificat SSL (Let's Encrypt)
- Firewall configuré
- Logs rotatifs
Performance :
collectstaticexécuté- Redis configuré
- CDN pour les assets
- Compression gzip activée
Monitoring :
- Sentry pour les erreurs
- Uptime monitoring
- Alertes Slack configurées
Cette liste paraît longue, mais elle se fait en 20 minutes quand on a l'habitude. Et elle évite 90% des problèmes classiques.
Chaque erreur de cette liste m'a coûté des heures de debug et des nuits sans sommeil. Mais elles m'ont aussi appris l'importance de ne pas prendre de raccourcis en production.
Django est un framework incroyable, mais il faut respecter ses règles. DEBUG=False, serveur WSGI approprié, monitoring actif, et backups réguliers. Ces quatre piliers peuvent sauver ton projet.
La prochaine fois que tu déploies du Django, garde cette liste sous les yeux. Tes futures insomnies te remercieront.
Tags
A lire aussi
Ma formule pour estimer un projet web sans me planter
Un mandat « refonte simple » estimé à 40 heures qui en prend 122 : le genre de dépassement qui te fait travailler à 26 $ l'heure sans t'en rendre compte. Voici la formule d'estimation que j'applique depuis, avec multiplicateur par type de client et marges fixes pour les zones grises récurrentes.
Facturer au forfait ou à l'heure : ce que j'ai appris en freelance
Le forfait mal cadré m'a fait travailler à moitié prix. Le taux horaire pur a puni ma rapidité. Voici comment j'ai fini par structurer mes devis avec un modèle hybride, cadrage payé puis heures plafonnées, et ce que ça a changé dans ma relation client.
React et TypeScript : quand dire non malgré la hype
Un site vitrine de cinq pages n'a aucune raison d'exister en React. Retour d'expérience d'un développeur au Québec sur le choix de stack : quand React et TypeScript valent vraiment le coup, et quand la hype coûte cher au client.

