Ma Photo
Yacine MEZIANE
Ingénieur Systèmes & Automatisation
Home CV Loisirs Blog Contact

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.

Mise à jour: mars 2026 | Auteur : Yacine