Infrastructure Portfolio DevSecOps : CI/CD, Docker & Sécurité | Mickael Chaksoukane | Portfolio

Infrastructure Portfolio DevSecOps : CI/CD, Docker & Sécurité

10 janvier 2026

Objectifs du Projet

L’objectif principal de ce projet était de concevoir, déployer et sécuriser une infrastructure d’hébergement pour mon site web Portfolio, en respectant les principes du DevSecOps.

Le but n’était pas juste de mettre un site en ligne, mais de le faire de manière professionnelle : tout doit être automatisé, versionné et sécurisé. Concrètement, les critères que je me suis fixés étaient :

  • Infrastructure as Code (IaC) : Aucune configuration manuelle sur le serveur, tout doit être scripté.
  • Conteneurisation : Utilisation exclusive de Docker pour isoler les services.
  • Sécurité (Hardening) : Durcissement du système hôte et des conteneurs.
  • CI/CD : Mise en production automatique à chaque git push.
  • Défense Active : Mise en place d’un Honeypot qui fera l’objet d’un autre projet.

Architecture Générale

L’idée centrale de ce projet est simple : je push du code, et quelques minutes plus tard le site est mis à jour en production, sans que j’ai à toucher quoi que ce soit sur le serveur. Tout le reste : sécurité, build, déploiement, est automatisé.

Voici le flux complet :

[Développeur]
    │  git push
    ▼
[GitHub Actions]
  ├── Scan secrets (Gitleaks)
  ├── Build Docker image
  ├── Scan vulnérabilités (Trivy)
  └── Push Docker Hub
           │
           ▼
    [Docker Hub]
           │  (Watchtower)
           ▼
    [Serveur de production]
      ├── Traefik (Reverse Proxy HTTPS)
      ├── Portfolio (site Hugo)
      ├── Honeypot HTTP (Flask — wp-admin)
      ├── Honeypot SSH (Cowrie)
      └── Wazuh SIEM (manager + indexer + dashboard)

Infrastructure as Code — Ansible

J’ai choisi Ansible pour provisionner le serveur. L’objectif est de pouvoir reconstruire l’environnement complet en une seule commande, sans jamais me connecter manuellement pour configurer quoi que ce soit à la main.

Le playbook main.yml est structuré en 4 grandes parties.

Partie 1 : Mise à jour système

La première chose que fait le playbook, c’est mettre à jour le système et installer les outils de base dont j’ai besoin :

- name: Mettre à jour le cache APT et les paquets
  apt:
    update_cache: yes
    upgrade: dist

- name: Installer les outils de base
  apt:
    name: [curl, git, ufw, fail2ban, htop, net-tools, ca-certificates, gnupg, sudo]
    state: present

Partie 2 : Installation Docker

Pour Docker, j’utilise la méthode officielle pour Debian 12 (Bookworm) : ajout de la clé GPG Docker et du dépôt signé, puis installation des paquets. C’est plus long à écrire qu’un simple apt install docker.io, mais c’est la méthode recommandée qui garantit d’avoir la dernière version stable.

- name: Installer Docker
  apt:
    name:
      - docker-ce
      - docker-ce-cli
      - containerd.io
      - docker-buildx-plugin
      - docker-compose-plugin
    state: present

J’ajoute aussi mon utilisateur au groupe docker pour pouvoir lancer des commandes Docker sans sudo.

Partie 3 : Hardening du serveur

Une fois Docker installé, le playbook applique un ensemble de mesures de durcissement sur le système hôte : configuration SSH selon les bonnes pratiques, mise en place d’un pare-feu avec des règles strictes en entrée, et installation d’un outil de protection contre le brute-force. L’idée est que le serveur soit aussi fermé que possible par défaut, et que seuls les flux strictement nécessaires soient autorisés.

Tout cela est géré automatiquement par Ansible, ce qui garantit que ces règles sont toujours appliquées, même si le serveur est reconstruit from scratch.

Partie 4 : Déploiement de la Stack

La dernière étape, c’est le déploiement de l’application. Ansible copie les fichiers de configuration sur le serveur, s’authentifie sur le registre Docker, puis démarre la stack :

- name: Démarrer la Stack (Compose Up)
  community.docker.docker_compose_v2:
    state: present
    pull: always

Architecture Docker & Micro-services

La stack Docker tourne avec 4 services répartis sur deux réseaux isolés.

Les services

Traefik — Reverse Proxy HTTPS

C’est le seul point d’entrée du trafic externe. Il reçoit toutes les requêtes, gère les certificats TLS, et les redirige vers le bon service selon l’URL. Il produit des access logs JSON utilisés pour la détection d’intrusion.

Portfolio — Site Hugo

C’est le conteneur qui fait tourner le site. Il est construit en multi-stage Docker pour garder une image finale la plus légère possible :

  1. Stage 1 (Builder) : génère le site statique avec minification.
  2. Stage 2 (Runtime) : sert uniquement les fichiers HTML/CSS/JS générés via Nginx Alpine.

Honeypots — HTTP et SSH

Deux leurres sont déployés. Le premier simule une interface WordPress (/wp-admin, /wp-login.php) via une application Flask qui capture les credentials soumis par les bots. Le second, Cowrie, émule un serveur SSH et enregistre chaque tentative de connexion et commande exécutée. Les deux sont sur un réseau interne isolé, inaccessibles directement depuis Internet.

Wazuh — SIEM

Wazuh est déployé en mode single-node (manager + indexer + dashboard) via Docker. Il centralise les alertes produites par les honeypots et le reverse proxy. L’agent Wazuh sur le host lit les volumes Docker des logs et remonte les événements au manager, qui applique les décodeurs et règles de détection. Le détail de la configuration est dans le projet suivant.

Watchtower — Mise à jour automatique

Watchtower surveille le registre Docker en permanence. Dès qu’une nouvelle image est publiée par le pipeline CI/CD, il la télécharge, remplace l’ancien conteneur et supprime l’ancienne image.

C’est ce qui ferme la boucle du déploiement continu : le pipeline pousse une nouvelle image, Watchtower détecte la mise à jour et redéploie automatiquement.

Isolation réseau

Deux réseaux Docker distincts permettent de cloisonner les services : un réseau accessible depuis l’extérieur pour les services publics, et un réseau entièrement interne pour les services de défense. Ces derniers ne sont jamais joignables directement depuis Internet.


Pipeline CI/CD — GitHub Actions

Le pipeline se déclenche automatiquement à chaque git push sur main ou master, à condition que des fichiers dans site/ ou infra/ aient été modifiés. Un déclenchement manuel est aussi possible.

Étape 1 — Scan de secrets (Gitleaks)

- name: Scan de Secrets (Gitleaks)
  uses: gitleaks/gitleaks-action@v2

Avant de faire quoi que ce soit, Gitleaks analyse l’historique complet du dépôt pour détecter des secrets qui auraient été commités par erreur : clés API, tokens, mots de passe. Si quelque chose est trouvé, le pipeline s’arrête immédiatement.

Étape 2 — Build Docker

- name: Build de l'image
  uses: docker/build-push-action@v4
  with:
    context: ./site
    load: true

L’image est construite localement (sans push pour l’instant) et taguée avec deux identifiants : latest pour le déploiement courant et le hash du commit pour la traçabilité, ce qui permet de revenir en arrière sur une version précise si besoin.

Étape 3 — Scan de vulnérabilités (Trivy)

- name: Scan de Vulnérabilités (Trivy)
  uses: aquasecurity/trivy-action@master
  with:
    exit-code: '1'
    ignore-unfixed: true
    severity: 'CRITICAL,HIGH'

Trivy scanne l’image pour détecter des CVE dans les paquets OS et les bibliothèques. Si une vulnérabilité CRITICAL ou HIGH avec un correctif disponible est trouvée, le pipeline bloque (exit-code: 1). Les vulnérabilités sans correctif sont ignorées (ignore-unfixed: true) pour éviter de bloquer sur des faux positifs.

Étape 4 — Push vers le registre

Le push ne se fait que si Gitleaks et Trivy sont passés proprement.


Défense Active — Honeypot

L’idée du honeypot est simple : les bots et les attaquants scannent en permanence les serveurs à la recherche d’interfaces d’administration accessibles. Certaines routes sont testées automatiquement par des milliers de bots chaque jour, souvent en quelques minutes après la mise en ligne d’un serveur.

Plutôt que de répondre avec une erreur, ce qui confirme à l’attaquant que la route n’existe pas, le proxy redirige silencieusement les requêtes suspectes vers le leurre. L’attaquant croit tomber sur quelque chose d’intéressant, alors qu’il est sur un service isolé qui ne mène nulle part.

L’instrumentation complète des honeypots — logging JSON, capture de credentials, remontée dans un SIEM Wazuh avec décodeurs et règles personnalisés — fait l’objet du projet suivant : Honeypot & SIEM : Détecter les Attaquants en Production.


Sécurité Globale — Résumé

NiveauMesureOutil
CodeDétection de secrets exposésGitleaks
ImageScan de vulnérabilités CVETrivy
SecretsChiffrement des variables sensiblesAnsible Vault + GitHub Secrets
HôteHardening système (SSH, pare-feu, brute-force)Ansible
RéseauIsolation des conteneursDocker networks
ApplicationLeurre attaquants (HTTP + SSH)Honeypot Flask + Cowrie
DétectionAlertes temps réel sur les intrusionsWazuh SIEM
Mise à jourPatching automatique des conteneursWatchtower

Ce que ce projet m’a apporté

Ce projet m’a permis de mettre en pratique toute la chaîne DevSecOps de bout en bout, ce qu’on voit rarement en cours : écrire le code, le versionner, le construire de façon reproductible, le sécuriser, et le déployer automatiquement.

La partie Ansible a été très intéressante. Écrire un playbook qui reproduit entièrement une configuration serveur, ça force à penser l’infrastructure de façon déclarative, si le serveur tombe, on peut tout reconstruire en une commande. C’est exactement la philosophie IaC qu’on cherche ici.

D’une manière générale, ce projet m’a permis de découvrir concrètement le DevSecOps, pas juste en théorie. Ansible par exemple, je connaissais le concept, mais c’est en l’utilisant sur un vrai serveur, avec de vrais problèmes à résoudre, que ça m’a vraiment parlé. C’est le genre de projet où on apprend plus en quelques semaines qu’en plusieurs cours.