1. Le Constat et les Objectifs Architecturaux

Dans le cadre de l’évolution de mes compétences en administration système et réseau, j’ai entrepris la conception d’une infrastructure d’hébergement “Enterprise Grade” sur un serveur physique local (On-Premise).

L’objectif initial était de dépasser la simple conteneurisation pour construire une architecture hautement résiliente, respectant les standards stricts de l’industrie :

  • Sécurité absolue (Zero Trust) : Refus catégorique de l’ouverture des ports sur le routeur de bordure (NAT/PAT) afin d’éliminer le risque d’attaques par force brute ou de balayage de ports (scanning).
  • Automatisation (IaC) : Toute l’infrastructure doit être décrite sous forme de code et reproductible.
  • Immutabilité (CI/CD) : Le code applicatif ne doit jamais être monté via des volumes locaux, mais scellé au sein d’artefacts (images Docker) versionnés et distribués via un registre privé.
  • Routage Dynamique (Layer 7) : L’exposition des services doit être gérée à la volée par un proxy inverse intelligent.

2. L’Infrastructure de Base : Réseau SDN (Software Defined Network)

La première étape de la sécurisation consiste à isoler les flux réseau. Au lieu d’utiliser le réseau bridge par défaut de Docker, un réseau virtuel dédié a été créé. Ce SDN permet à nos futurs conteneurs de communiquer entre eux via une résolution DNS interne stricte, tout en restant invisibles depuis l’extérieur.

# Création du sous-réseau isolé pour le reverse proxy
docker network create proxy_net

3. Le Cœur du Routage : Déploiement de Traefik

Pour aiguiller le trafic entrant vers les bons conteneurs applicatifs (le registre, le site web, etc.), j’ai déployé Traefik, un reverse proxy et load-balancer cloud-native.

L’avantage majeur de Traefik réside dans sa capacité à écouter l’API de Docker. Il détecte automatiquement l’apparition de nouveaux conteneurs (via leurs labels) et reconfigure ses routes (Layer 7) en temps réel, sans nécessiter de redémarrage.

Voici le manifeste docker-compose.yml définissant le routeur principal, configuré pour accepter le trafic HTTP pur depuis le tunnel de sécurité (que nous déploierons plus tard).

services:
  traefik:
    image: traefik:latest
    container_name: traefik_proxy
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy_net
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./dynamic_conf:/dynamic_conf:ro
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.file.directory=/dynamic_conf"
      - "--providers.file.watch=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--api.dashboard=true"
      - "--api.insecure=true"
      - "--serverstransport.insecureskipverify=true"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`traefik.local`)"
      - "traefik.http.routers.api.service=api@internal"
      - "traefik.http.routers.api.entrypoints=web"

networks:
  proxy_net:
    external: true

Dashboard Traefik listant les routeurs HTTP et TCP actifs

4. Le Pipeline CI/CD : Déploiement d’un Registre Privé

Dans une approche DevOps stricte, la compilation du code et son exécution sont deux étapes distinctes. Pour éviter de dépendre du Docker Hub public ou de compiler du code en direct sur le serveur de production, l’architecture intègre son propre Registre Docker Privé.

L’objectif est d’avoir un coffre-fort local où sont stockées de manière chiffrée les images immuables (Artefacts) générées par les pipelines CI. C’est le conteneur final (Nginx) qui ira tirer (Pull) cette image depuis ce registre.

Pour autoriser les transferts sécurisés entre le moteur Docker (hôte) et ce registre, un ensemble de certificats cryptographiques locaux a été généré via mkcert et injecté dans le dossier d’approbation système /etc/docker/certs.d/.

Voici le manifeste docker-compose.yml du registre, routé par Traefik sur un domaine interne virtuel :

services:
  registry:
    image: registry:2
    container_name: docker_registry
    restart: unless-stopped
    networks:
      - proxy_net
    security_opt:
      - no-new-privileges:true
    volumes:
      - ./registry-data:/var/lib/registry
      - ./auth:/auth
      # Configuration TLS
      - /opt/docker/certs:/certs:ro
    environment:
      - REGISTRY_AUTH=htpasswd
      - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
      - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
      - REGISTRY_HTTP_ADDR=0.0.0.0:5000
      # Force le registre à générer des URL de redirection HTTPS pour les blobs
      - REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.local.pem
      - REGISTRY_HTTP_TLS_KEY=/certs/registry.local-key.pem
    labels:
      - "traefik.enable=true"
      # Accès exclusif sur le domaine virtuel local
      - "traefik.http.routers.registry.rule=Host(`registry.local`)"
      - "traefik.http.routers.registry.entrypoints=websecure"
      # Délégation de la terminaison TLS au registre (Passthough)
      - "traefik.tcp.routers.registry.rule=HostSNI(`registry.local`)"
      - "traefik.tcp.routers.registry.tls.passthrough=true"
      - "traefik.tcp.services.registry.loadbalancer.server.port=5000"

networks:
  proxy_net:
    external: true

Mécanique de l’Artefact (Build & Push)

Grâce à ce composant critique, le workflow de déploiement d’une nouvelle version applicative (ex: v2.3) se résume à une séquence rigoureuse sans aucune altération de la production en cours :

# 1. Compilation de l'image (Scellement du code source dans Nginx)
docker build -t registry.local/portfolio_web:v2.3 .

# 2. Transfert sécurisé de la nouvelle couche vers le registre local
docker push registry.local/portfolio_web:v2.3

Terminal Ubuntu affichant la réussite de la commande docker push vers le registre local avec son empreinte SHA256

5. L’Exposition Sécurisée : Cloudflare Tunnels (Zero Trust)

Exposer un serveur local directement sur Internet via une redirection de ports (NAT/PAT) sur un routeur expose l’infrastructure à des attaques par balayage (scanning) et par force brute. Lors des premiers tests d’exposition sur IP publique, la couche réseau physique a montré des signes de saturation (lags SSH, perte de paquets) liés au bruit de fond d’Internet.

Pour pallier cette vulnérabilité, le paradigme Zero Trust a été implémenté via Cloudflare Tunnels.

Le principe : Aucun port entrant n’est ouvert sur le pare-feu. Un démon local (cloudflared) initie une connexion sortante vers le réseau mondial de Cloudflare (Edge) via le protocole QUIC (UDP). Le trafic des visiteurs est chiffré par Cloudflare, envoyé dans ce tunnel, puis déchargé en HTTP pur vers Traefik. Le serveur devient littéralement invisible sur Internet.

Voici le manifeste docker-compose.yml du connecteur, placé sur le même réseau proxy_net pour qu’il puisse résoudre le nom de Traefik :

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared_tunnel
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy_net
    environment:
      # Injection propre du secret de liaison via variable d'environnement
      - TUNNEL_TOKEN=VOTRE_TOKEN_CLOUDFLARE
    command: tunnel --no-autoupdate run

networks:
  proxy_net:
    external: true

Interface Cloudflare Zero Trust affichant le connecteur réseau au statut Healthy et connecté au datacenter

6. Hébergement Final : L’Artefact Immuable (Hugo + Nginx)

Le portfolio est rédigé en Markdown et compilé via Hugo, un générateur de site statique ultra-rapide. Plutôt que de monter les fichiers HTML générés via un volume (Bind Mount) - ce qui briserait l’immutabilité du déploiement - les fichiers sont scellés directement à l’intérieur d’une image Nginx.

Voici la recette (Dockerfile) utilisée pour construire l’artefact :

FROM nginx:alpine

# Suppression de la page par défaut de Nginx
RUN rm -rf /usr/share/nginx/html/*

# Copie du site généré par Hugo à l'intérieur de l'image Nginx
COPY ./public /usr/share/nginx/html

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Une fois compilée et poussée dans le registre privé (via l’étape 4), l’application est instanciée via son manifeste de production. Traefik lit les labels dynamiquement et route le trafic issu du tunnel vers ce conteneur Nginx.

services:
  web:
    # L'image est tirée du registre privé local
    image: registry.local/portfolio_web:v2.3
    container_name: portfolio_web
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy_net
    labels:
      - "traefik.enable=true"
      # Routage dynamique basé sur le domaine public intercepté par Cloudflare
      - "traefik.http.routers.portfolio.rule=Host(`aghilesportfolio.com`)"
      - "traefik.http.routers.portfolio.entrypoints=web"
      - "traefik.http.services.portfolio.loadbalancer.server.port=80"

networks:
  proxy_net:
    external: true

Tableau de bord Portainer listant l’ensemble de la stack d’infrastructure réseau en cours d’exécution (Up)

Conclusion

Cette infrastructure valide la mise en œuvre d’une architecture robuste, Enterprise-Grade. Le couplage du SDN de Docker, du routage Layer 7 de Traefik et des tunnels Cloudflare permet d’obtenir :

  • Une sécurité maximale (Aucun port exposé, IP publique masquée, terminaison SSL gérée par un CDN mondial).
  • Une agilité de déploiement digne d’une chaîne CI/CD (Build/Tag/Push locaux).
  • Une capacité de reprise sur sinistre immédiate : en cas de régression, il suffit de modifier le tag de l’image dans le fichier compose (ex: passage de v2.3 à v2.2) et de redéployer pour effectuer un rollback instantané.