Conteneurisation et déploiement d’une architecture Django en production
Table des matières
Prérequis
Avant de suivre ce tutoriel, plusieurs éléments doivent être en place
afin de garantir un déploiement fluide et sans conflit :
-
un serveur Linux disposant de Docker et Docker Compose installés,
-
un nom de domaine configuré et pointant vers l’adresse IP du
serveur,
-
des certificats Let’s Encrypt déjà générés ou un accès root
permettant de les créer,
-
l’arrêt des services occupant les ports 80 et 443 (notamment
Apache), afin de permettre à Nginx dans Docker de s’y lier
correctement,
-
un accès SSH au serveur et les permissions nécessaires pour
manipuler les services système,
- une connaissance de base de Git, Docker et Django.
Résumé
Ce tutoriel décrit l’architecture complète d’un environnement de
production Django conteneurisé, structuré autour d’une séparation
nette des responsabilités et d’un découplage strict entre les
différentes couches applicatives. L’objectif est de mettre en place
une infrastructure robuste, maintenable et reproductible, où chaque
composant fonctionne dans son propre conteneur et communique via un
réseau interne Docker.
Architecture
L’architecture de la plateforme repose sur une approche conteneurisée
basée sur Docker. Les différents services sont séparés en couches
indépendantes afin d'améliorer la maintenabilité, la sécurité et la
capacité d'évolution de l'application. Cette approche remplace
l’ancienne architecture monolithique où Apache, Django et PostgreSQL
étaient installés sur une seule machine.
Chaque composant possède désormais une responsabilité claire et
fonctionne dans son propre conteneur ou service.
-
Application Layer — Django + Gunicorn
L’application Django est empaquetée dans une image Docker
immuable. Gunicorn agit comme serveur WSGI et exécute
l'application Python. Cette couche est dédiée exclusivement à la
logique métier : gestion des vues, API, templates et interactions
avec la base de données.
-
Data Layer — PostgreSQL
La base de données PostgreSQL fonctionne dans un conteneur Docker
dédié avec un volume persistant afin de garantir la conservation
des données même si le conteneur est recréé. Cette approche est
parfaitement adaptée pour un projet personnel ou une petite
infrastructure auto-hébergée. Dans les architectures d’entreprise,
la base de données est généralement externalisée vers un service
managé (par exemple AWS RDS, Google Cloud SQL ou Azure Database).
Ces services offrent automatiquement la haute disponibilité, les
sauvegardes automatisées, le monitoring et la gestion des mises à
jour de sécurité.
-
Network & Routing Layer — Nginx
Nginx agit comme point d’entrée de l’application et assure
plusieurs responsabilités critiques :
- terminaison TLS via Let’s Encrypt,
- redirection automatique HTTP → HTTPS,
- distribution efficace des fichiers statiques et médias,
- reverse proxy vers les processus Gunicorn.
-
Security & Certification Layer — Certbot
Certbot automatise la génération et le renouvellement des
certificats SSL via Let’s Encrypt. Les certificats sont stockés
sur la machine hôte et partagés avec le conteneur Nginx via un
bind mount. Cela permet à Nginx d'utiliser directement les
certificats générés sans interruption de service lors des
renouvellements.
Internet
|
| (80 / 443)
v
+----------------+
| Nginx |
| container |
+----------------+
| | |
| | |
| | +------+
| | |
(8000) | | |
| v |
| [static_volume] |
| ^ v
| | [media_volume]
| | ^
v | |
+----------------+
| Django |
| container |
+----------------+
|
|
| (5432)
v
+----------------+
| PostgreSQL |
| container |
+----------------+
|
v
[postgres_volume]
Pourquoi cette architecture est plus robuste
Cette architecture conteneurisée offre une approche beaucoup plus
moderne que l’ancien modèle Apache + Django + PostgreSQL sur un seul
serveur. Elle améliore la sécurité, les performances et la capacité
d’évolution de l’infrastructure.
1. Séparation claire des responsabilités
Dans l’ancienne architecture, Apache servait à la fois de serveur
HTTP, exécutait Django via mod_wsgi, servait les fichiers statiques et
cohabitait avec PostgreSQL sur la même machine.
Désormais chaque composant possède un rôle précis :
-
Nginx : gestion du trafic HTTP/HTTPS et reverse
proxy
- Django : logique applicative
-
PostgreSQL : stockage et persistance des données
2. Meilleures performances avec Nginx
Nginx est particulièrement performant pour la gestion des connexions
concurrentes et la distribution de contenu statique.
- gestion HTTPS moderne et optimisée
- meilleure performance pour les fichiers statiques
- configuration simple du reverse proxy
-
possibilité d’ajouter du rate limiting et des headers de sécurité
3. Application Django plus légère
Django fonctionne désormais dans un conteneur indépendant avec
Gunicorn.
Cela facilite :
- les mises à jour de l’application
- la reproductibilité des environnements
- les déploiements automatisés
4. Base de données isolée
La base de données est isolée dans son propre conteneur afin d’éviter
toute dépendance avec l’application.
Cette séparation facilite les sauvegardes, les migrations et les mises
à jour indépendantes du reste de l’application.
5. Gestion propre de la persistance
Les données persistantes sont séparées dans des volumes dédiés :
- volume pour les fichiers statiques
- volume pour les fichiers médias
- volume pour les données PostgreSQL
Cette séparation améliore la gestion des sauvegardes et simplifie les
migrations d'infrastructure.
6. Architecture prête pour le scaling
Contrairement à l’ancienne architecture mono-serveur, cette structure
permet une évolution horizontale.
- plusieurs conteneurs Django peuvent être déployés
- Nginx peut répartir la charge entre plusieurs instances
-
les fichiers statiques peuvent être externalisés vers S3 ou GCS
7. Compatible CI/CD et cloud-native
La conteneurisation facilite les déploiements automatisés et
l’intégration dans des pipelines CI/CD.
Cette architecture est également facilement migrable vers des
orchestrateurs comme Kubernetes.
Limites
- architecture plus complexe qu’un serveur monolithique
- multiplication des conteneurs à gérer
-
nécessite de comprendre les réseaux Docker (communication entre
conteneurs, exposition des ports, reverse proxy)
Conclusion
Cette architecture modulaire constitue une base solide pour une
application Django moderne. Elle améliore la sécurité, la performance
et la capacité d’évolution tout en s’alignant avec les pratiques
DevOps et cloud-native actuelles.
I. Construire l’image Django
1. Récupérer le code source
La première étape consiste à cloner le dépôt Git contenant le code de
l’application Django. Cette opération garantit l’utilisation d’une
version précise et maîtrisée du projet pour l’environnement de
production.
git clone git@github.com:MEZIANE-tech/Portfolio.git
Pour assurer une cohérence entre les environnements de développement
et de production, le fichier settings.py a été ajusté. En
développement, les variables sensibles étaient chargées depuis un
fichier .env situé dans src/Portfolio,
contenant notamment les informations de connexion à PostgreSQL.
Avant :
env = environ.Env()
environ.Env.read_env(env_file=str(BASE_DIR / "Portfolio" / ".env"))
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'portfolio',
'USER': 'portfolioadmin',
'PASSWORD': env("PSQL_PASSWORD"),
'HOST': env("PSQL_HOST"),
'PORT': '5432'
}
}
Cette approche n’était pas adaptée à Docker, car en production :
-
le fichier
.env n’est plus situé dans
src/Portfolio,
-
le host PostgreSQL n’est plus une IP locale mais le service Docker
db.
Pour unifier les environnements, une variable
ENV a été
introduite (
development /
production) afin d’adapter
automatiquement le comportement de Django.
ENV = os.getenv("ENV", "development")
if ENV == "development":
environ.Env.read_env(env_file=str(BASE_DIR.parent / ".env"))
La configuration de la base de données a ensuite été séparée selon
l’environnement.
if ENV == "development":
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'portfolio',
'USER': 'portfolioadmin',
'PASSWORD': env("PSQL_PASSWORD"),
'HOST': env("PSQL_HOST"),
'PORT': '5432'
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'portfolio',
'USER': 'portfolioadmin',
'PASSWORD': env("PSQL_PASSWORD"),
'HOST': "db",
'PORT': '5432'
}
}
Remarque : la variable ENV est désormais
définie dans le fichier .env, automatiquement chargé par
Docker Compose lors du lancement de la stack.
2. Fichier .dockerignore
Le fichier .dockerignore indique à Docker quels éléments
ne doivent pas être copiés dans l’image lors du build. L’objectif est
d’exclure les environnements virtuels, les dépôts Git, les secrets,
les fichiers compilés et les artefacts générés.
# Virtualenv Python
../.venv
# Repo Git
../.git
# Secrets Django
../.env
# Fichiers inutiles
__pycache__
*.pyc
# Fichiers générés
staticfiles
media
Détails :
-
.env contient des secrets et ne doit jamais être
intégré dans l’image.
-
.git regroupe l’historique du projet et n’a aucune
utilité en production.
-
.venv correspond à l’environnement virtuel local,
inutile dans Docker.
-
Les fichiers compilés Python (
__pycache__,
*.pyc) sont exclus.
-
Les répertoires
staticfiles et
media sont générés à l’exécution et ne doivent pas
être intégrés dans l’image.
3. Dockerfile
Le Dockerfile décrit la procédure de construction de l’image de
production. Il installe les dépendances système et Python, copie le
code source, puis configure Gunicorn comme serveur WSGI.
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app/
EXPOSE 8000
CMD ["gunicorn", "Portfolio.wsgi:application", "--bind", "0.0.0.0:8000"]
Points clés :
-
python:3.11-slim fournit une base légère adaptée à la
production.
-
PYTHONDONTWRITEBYTECODE empêche la création de
fichiers .pyc.
-
PYTHONUNBUFFERED garantit l’affichage immédiat des
logs.
-
build-essential et libpq-dev sont
nécessaires pour compiler certaines dépendances, notamment
PostgreSQL.
-
COPY requirements.txt optimise le cache Docker.
-
pip install installe Django, Gunicorn, psycopg2, etc.
-
COPY . /app copie uniquement les fichiers utiles
grâce au .dockerignore.
-
EXPOSE 8000 documente le port utilisé par Gunicorn.
-
CMD gunicorn lance l’application en mode production.
4. Build de l’image
L’image Docker est construite pour l’architecture ARM64 (Raspberry Pi)
et taguée
prod afin d’identifier clairement la version destinée à
la production.
docker buildx build --platform linux/arm64 -t yacineryujin/portfolio:prod ./src
Détails :
-
buildx permet la construction multi‑plateforme.
-
--platform linux/arm64 cible l’architecture du
Raspberry Pi.
-t attribue un nom et un tag à l’image.
-
./src indique le répertoire contenant le code source.
5. Push Docker Hub
a. Authentification Docker Hub
Une authentification est nécessaire pour publier l’image sur Docker
Hub. L’utilisation d’un token d’accès est recommandée pour des raisons
de sécurité.
docker login -u yacineryujin
Remarque : l’utilisation d’un mot de passe est
possible mais déconseillée. Un token d’accès dédié est préférable.
b. Publication de l’image
L’image yacineryujin/portfolio:prod est ensuite envoyée
sur Docker Hub afin d’être accessible pour le déploiement.
docker push yacineryujin/portfolio:prod
II. Préparer la stack Docker Compose
1. Arborescence
Ce répertoire regroupe l’ensemble des éléments nécessaires au
déploiement : configuration Nginx, fichiers Certbot, fichier Docker
Compose et variables d’environnement.
/srv/portfolio
├── certbot/
├── nginx/
├── docker-compose.prod.yml
└── .env
Remarque : avant la mise en place de Docker,
l’application fonctionnait directement sur le serveur hôte, où Certbot
était déjà installé et configuré. Les certificats Let’s Encrypt
étaient donc disponibles. Après la migration vers Docker, ces
certificats ont simplement été réutilisés via un volume, ce qui a
évité une nouvelle procédure de génération. Pour une installation
neuve, il est nécessaire de suivre la documentation officielle de
Let’s Encrypt pour obtenir et renouveler les certificats.
2. Configuration Nginx
Nginx assure plusieurs rôles essentiels :
- rediriger le trafic HTTP vers HTTPS,
- servir les fichiers statiques et médias,
-
agir comme reverse proxy vers l’application Django exécutée par
Gunicorn.
La configuration repose sur deux serveurs : un serveur HTTP dédié à
Let’s Encrypt et aux redirections, et un serveur HTTPS qui traite les
requêtes applicatives.
a. Serveur HTTP (port 80) — validation Let’s Encrypt et
redirection
server {
listen 80;
server_name yacinemeziane.fr www.yacinemeziane.fr;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
return 301 https://$host$request_uri;
}
Ce serveur HTTP remplit deux fonctions :
-
permettre la validation Let’s Encrypt via le répertoire
/.well-known/acme-challenge/,
-
rediriger l’intégralité du trafic HTTP vers HTTPS grâce à une
redirection permanente
301.
b. Serveur HTTPS (port 443) — reverse proxy vers Django
server {
listen 443 ssl;
server_name yacinemeziane.fr www.yacinemeziane.fr;
ssl_certificate /etc/letsencrypt/live/yacinemeziane.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yacinemeziane.fr/privkey.pem;
}
Ce bloc active HTTPS et indique à Nginx l’emplacement des certificats
Let’s Encrypt, accessibles dans le conteneur via un bind mount
pointant vers le répertoire /etc/letsencrypt de l’hôte.
c. Gestion des fichiers statiques et médias
location /static/ { alias /static/; }
location /media/ { alias /media/; }
Ces directives permettent à Nginx de servir directement les fichiers
statiques et les fichiers uploadés, sans solliciter Django ni
Gunicorn, ce qui améliore les performances globales.
d. Reverse proxy vers Gunicorn
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
Ce bloc transmet les requêtes applicatives au service Docker
web
(Gunicorn). Les en‑têtes envoyés préservent le nom de domaine,
l’adresse IP du client et indiquent à Django que la requête est
sécurisée.
e. Configuration complète
server {
listen 80;
server_name yacinemeziane.fr www.yacinemeziane.fr;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name yacinemeziane.fr www.yacinemeziane.fr;
ssl_certificate /etc/letsencrypt/live/yacinemeziane.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yacinemeziane.fr/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location /static/ { alias /static/; }
location /media/ { alias /media/; }
location / {
proxy_pass http://web: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 https;
}
}
3. Configuration Docker Compose
Le fichier
docker-compose.prod.yml orchestre l’ensemble
de la stack :
- l’application Django exécutée par Gunicorn,
- la base de données PostgreSQL,
- Nginx en tant que reverse proxy HTTPS.
Il définit également les volumes persistants utilisés pour les
fichiers statiques, les médias et les données PostgreSQL.
a. Service web — Application Django + Gunicorn
web:
image: yacineryujin/portfolio:prod
command: gunicorn Portfolio.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_volume:/app/staticfiles
- media_volume:/app/media
env_file:
- .env
depends_on:
- db
Ce service exécute l’application Django à partir de l’image
yacineryujin/portfolio:prod. Gunicorn assure le rôle de
serveur WSGI en production.
- command : lance Gunicorn sur le port 8000.
-
volumes : stocke les fichiers statiques et médias
dans des volumes persistants.
-
env_file : charge les variables d’environnement de
production.
-
depends_on : garantit que PostgreSQL est
opérationnel avant Django.
b. Service db — Base de données PostgreSQL
db:
image: postgres:15
environment:
POSTGRES_DB: portfolio
POSTGRES_USER: portfolioadmin
POSTGRES_PASSWORD: ${PSQL_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
Ce service déploie PostgreSQL 15 avec un utilisateur dédié et un
volume persistant pour conserver les données, même en cas de
suppression du conteneur.
c. Service nginx — Reverse proxy + HTTPS
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- static_volume:/static
- media_volume:/media
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- /etc/letsencrypt:/etc/letsencrypt:ro
- ./certbot/www:/var/www/certbot
depends_on:
- web
Nginx sert de reverse proxy devant Gunicorn et gère le HTTPS grâce aux
certificats Let’s Encrypt montés en lecture seule. Les ports 80 et 443
sont exposés pour permettre l’accès public au site.
-
static_volume / media_volume :
servent les fichiers statiques et médias.
-
default.conf : configuration Nginx personnalisée.
-
/etc/letsencrypt : certificats SSL existants.
- /var/www/certbot : validation Let’s Encrypt.
Le service dépend du service web, garantissant que
l’application Django est opérationnelle avant la mise en place du
reverse proxy.
d. Déclaration des volumes persistants
volumes:
static_volume:
media_volume:
postgres_data:
Ces volumes assurent la persistance des fichiers statiques, des médias
et des données PostgreSQL, indépendamment du cycle de vie des
conteneurs.
III. Mise en service de l’infrastructure Docker
1. Vérifier la configuration
Avant de lancer la stack, il est important de s’assurer que le fichier
docker-compose.prod.yml est valide. Cette étape permet de
détecter immédiatement les erreurs de syntaxe, de variables
d’environnement ou de volumes mal définis.
docker compose -f docker-compose.prod.yml config
2. Démarrer l’infrastructure en production
Le lancement de la stack initialise les volumes persistants, crée le
réseau Docker et démarre les services dans l’ordre prévu. Une fois
cette étape terminée, Django, PostgreSQL et Nginx fonctionnent
ensemble en environnement de production.
docker compose -f docker-compose.prod.yml up -d
Remarque :l’application fonctionnait auparavant
directement sur le serveur hôte, où Apache gérait le trafic HTTP et
HTTPS. Comme Apache écoute sur les ports 80 et 443, ces ports restent
occupés tant que le service est actif. Pour permettre à Nginx (dans
Docker) d’utiliser ces ports, il est nécessaire d’arrêter Apache avant
de lancer la stack.
3. Contrôle de l’état des services
Une fois la stack démarrée, il est utile de vérifier que tous les
services sont bien en cours d’exécution. Le tableau affiché indique
l’état de chaque conteneur ainsi que les ports exposés, notamment ceux
de Nginx (80 et 443).
docker compose -f docker-compose.prod.yml ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
portfolio_db postgres:15 "docker-entrypoint.s…" db 21 minutes ago Up 21 minutes 5432/tcp
portfolio_nginx nginx:latest "/docker-entrypoint.…" nginx 21 minutes ago Up 21 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp
portfolio_web yacineryujin/portfolio:prod "gunicorn Portfolio.…" web 21 minutes ago Up 21 minutes 8000/tcp
IV. Migration des données
1. Export et restauration PostgreSQL
a. Exporter l’ancienne base de données
L’objectif est de récupérer une sauvegarde complète de l’ancienne base
PostgreSQL afin de pouvoir la restaurer dans l’environnement Docker.
Le dump est généré au format compressé (-F c) pour une
restauration fiable.
sudo su - postgres -c "pg_dump -d portfolio -F c -f /tmp/portfolio_db.backup"
b. Transférer la sauvegarde dans le conteneur PostgreSQL
Une fois le dump créé, il doit être copié à l’intérieur du conteneur
PostgreSQL afin d’être restauré directement depuis l’environnement
Docker.
docker cp /tmp/portfolio_db.backup portfolio_db:/tmp/portfolio_db.backup
c. Restaurer la base dans l’environnement Docker
La restauration injecte l’intégralité des données dans la nouvelle
base PostgreSQL utilisée par la stack. Cette opération remplace le
contenu existant et remet la base dans l’état exact de l’ancienne
production.
docker compose -f docker-compose.prod.yml exec db pg_restore -U portfolioadmin -d portfolio /tmp/portfolio_db.backup
Remarque : la restauration utilise l’utilisateur
portfolioadmin car, lors de la création du conteneur
PostgreSQL, le fichier docker-compose définit
explicitement un utilisateur. Dans cette configuration, PostgreSQL ne
crée pas le compte par défaut postgres, seul
l’utilisateur spécifié (portfolioadmin) existe dans le
conteneur. Il est donc normal et attendu d’utiliser cet utilisateur
pour la restauration.
2. Migrations Django
Une fois les données restaurées, il est essentiel de s’assurer que le
schéma correspond bien à la version actuelle du code. Les migrations
Django ajustent automatiquement les tables si des évolutions ont été
introduites depuis la dernière version.
docker compose -f docker-compose.prod.yml exec web python manage.py migrate
3. Collecte des fichiers statiques
Les fichiers statiques doivent être regroupés dans le volume partagé
avec Nginx afin d’être servis correctement en production. Cette étape
génère le dossier staticfiles utilisé par le reverse
proxy.
docker compose -f docker-compose.prod.yml exec web python manage.py collectstatic --noinput
Perspectives et évolutions à venir
L’architecture présentée constitue une base solide pour exécuter une
application Django en production. Plusieurs améliorations sont prévues
afin d’enrichir l’environnement, renforcer la sécurité et automatiser
davantage les opérations.
Les prochaines étapes incluront :
✔️ Intégration CI/CD avec Jenkins
Mise en place d’un pipeline complet permettant d’automatiser la
construction des images Docker, l’exécution des tests, le
déploiement en production et la gestion des versions. L’objectif est
d’obtenir un processus de livraison fiable, reproductible et
entièrement automatisé.
✔️ Monitoring système avec Netdata
Déploiement d’un outil de supervision en temps réel pour surveiller
l’état du serveur hôte (CPU, RAM, I/O, réseau) ainsi que les
conteneurs Docker. Cette couche de monitoring permettra de détecter
rapidement les anomalies et de renforcer la sécurité du Raspberry Pi
ou du serveur utilisé.
✔️ Centralisation des logs avec Loki
Mise en place d’une solution de collecte et d’agrégation des logs
afin de faciliter l’analyse des événements applicatifs et système.
Loki permettra de centraliser les logs de tous les conteneurs et
d’améliorer la visibilité sur le comportement de l’infrastructure.
✔️ Sauvegardes automatisées PostgreSQL et médias
Mise en place d’un système de sauvegarde régulier pour la base de
données et les fichiers médias. L’objectif est de garantir la
résilience des données et de permettre une restauration rapide en
cas d’incident.
✔️ Observabilité avancée avec Prometheus et Grafana
Ajout d’une couche d’observabilité complète incluant métriques,
tableaux de bord et alertes. Prometheus collectera les métriques des
services, tandis que Grafana offrira une visualisation claire et
personnalisable de l’état de l’infrastructure.
Ces évolutions permettront de transformer progressivement cette
architecture en une plateforme de production complète, automatisée,
sécurisée et entièrement observable. Elles offriront une vision plus
globale de l’état du système, tout en renforçant la fiabilité et la
maintenabilité de l’ensemble.