← Retour aux projets Projet 03 / 04
Étude de cas · Auto-hébergement

Ce portfolio, auto-hébergé

Le site que vous lisez tourne sur un serveur Debian chez moi, derrière ma box, sur le domaine m3tsh.com. HTTPS obligatoire et automatique, aucun service interne exposé, et une API de contact isolée qui me notifie sur Telegram.

Tester le formulaire →
Rôle
Conception, déploiement & exploitation
Cadre
Projet personnel · 2026
Hébergement
Serveur Debian à domicile
Stack
Docker · Caddy · Python
DNS IONOS m3tsh.com → A VISITEUR navigateur LET'S ENCRYPT défi TLS-ALPN-01 BOX NAT 443→4567 · 80→8080 HTTPS 443 SERVEUR DEBIAN · DOCKER COMPOSE · RÉSEAU « web » :4567 Caddy TLS · EN-TÊTES · CSP REVERSE PROXY SITE STATIQUE HTML · CSS · JS monté en lecture seule API CONTACT Python · bibli. standard utilisateur non-root VOLUME messages / /api/* TELEGRAM Bot API notification ARCHITECTURE RÉELLE · SEUL CADDY EST EXPOSÉ · AUCUN SERVICE INTERNE ACCESSIBLE DEPUIS INTERNET
01 — Objectifs

Héberger moi-même,
sans rien exposer d'inutile.

Plutôt qu'un hébergeur clé en main, je voulais un site qui soit lui-même une démonstration de ce que je sais faire :

  • Auto-hébergé sur un serveur Debian chez moi, derrière une box grand public.
  • HTTPS partout, avec un certificat Let's Encrypt renouvelé automatiquement.
  • Surface d'attaque minimale : seul Caddy est joignable depuis Internet (port 443, et port 80 pour rediriger vers HTTPS), aucun service interne joignable.
  • Un formulaire de contact qui fonctionne vraiment, sans service tiers côté visiteur.
  • Reproductible : tout le déploiement tient dans un dépôt et une commande.
02 — Architecture

Un seul point d'entrée.

Le nom de domaine m3tsh.com (DNS chez IONOS) pointe vers l'IP publique de ma box. La box redirige le port 443 vers le port 4567 du serveur, où écoute Caddy, le seul conteneur publié. Le port 80 arrive aussi sur Caddy, qui se contente de rediriger vers HTTPS.

Caddy termine le TLS, ajoute les en-têtes de sécurité, sert le site statique et transmet les requêtes /api/* à l'API de contact. Cette API n'est joignable que sur le réseau Docker interne : elle n'a aucun port publié.

$ ports exposés 443 → 4567Caddy · HTTPS OUVERT 80 → 8080HTTP · redirection 301 vers HTTPS REDIRIGE 8000API contact · réseau Docker INTERNE
Surface exposée — Caddy seul
FORMULAIRE CADDY16 Ko max APIcontrôles · 5/h/IP JSONL TELEGRAM POST /api/contact
Parcours d'un message de contact
03 — Mise en œuvre

Deux conteneurs, une commande.

Tout est décrit dans un fichier Docker Compose ; la configuration sensible (domaine, token du bot) vit dans un fichier .env non versionné. Un docker compose up -d suffit à tout remonter.

# docker-compose.yml (résumé)
caddy:
  image:    caddy:2.10
  ports:    "4567:4567"        # HTTPS (443 sur la box)
            "8080:8080"        # HTTP → redirection HTTPS
  volumes:  ./site:/srv/site:ro  # site en lecture seule
            caddy_data:/data     # certificats
contact:
  build:    ./contact          # python:3.13-alpine
  expose:   "8000"             # réseau interne uniquement
  # tourne en non-root (uid 10001, défini dans le Dockerfile)
  volumes:  messages:/data
  env:      TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
  • Certificat : Caddy l'obtient seul via le défi TLS-ALPN-01, qui passe par le port 443. Le port 80 ne sert qu'à rediriger les visiteurs vers HTTPS.
  • Domaine : www.m3tsh.com redirige en 301 vers m3tsh.com.
  • Formulaire : l'API (Python, bibliothèque standard uniquement) enregistre chaque message dans un volume puis m'envoie une notification via l'API Bot de Telegram.
  • Performance : compression zstd/gzip et cache navigateur sur les fichiers statiques.
04 — Sécurité

Défense en profondeur,
même pour un petit site.

  • Réseau : seuls les ports 443 et 80 sont redirigés, tous deux vers Caddy (le 80 ne fait que rediriger vers HTTPS) ; l'API n'est publiée sur aucun port de l'hôte.
  • En-têtes HTTP : HSTS, Content-Security-Policy stricte (aucun script externe ni inline), X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy, en-tête Server masqué.
  • Formulaire : corps limité à 16 Ko par Caddy, validation des champs côté serveur, champ piège anti-robot, limite de 5 messages par heure et par IP.
  • Conteneurs : API exécutée sous un utilisateur non-root, site monté en lecture seule, aucune dépendance Python externe à maintenir.
  • Secrets : fichier .env en droits 600, exclu du dépôt.
05 — Agent IA

Mis en ligne en pilotant
un agent IA.

Ce site a été conçu et déployé en travaillant avec Claude Code, un agent IA qui agit directement dans le terminal du serveur : il lit les fichiers, exécute des commandes et propose des modifications, que je valide une par une.

Mon rôle : fixer les objectifs et les contraintes, faire les choix d'architecture, contrôler chaque action et vérifier le résultat. Quelques exemples concrets du déploiement :

  • Passage à mon domaine : retrait du DNS dynamique, nouveau mode d'obtention du certificat sans ouvrir le port 80.
  • Erreur TLS au lancement : diagnostic d'un cache DNS de la box qui pointait encore vers l'ancienne IP.
  • Mails non reçus : le relais SMTP bloquait l'IP du serveur ; décision de passer à des notifications Telegram, plus simples et plus fiables.
  • Affichage mobile : captures automatisées dans un navigateur headless pour repérer et corriger les défauts.
  • Notifications absentes : configuration correcte mais conteneur relancé sans reconstruire son image, donc encore sur l'ancien code ; corrigé avec docker compose up -d --build.

Savoir comment fonctionne un modèle de langage — contexte, outils, limites — permet de lui confier de vraies tâches d'administration tout en gardant la main.

1Service exposé (Caddy)
2Conteneurs
0Dépendance externe (API)