xpeditis2.0/docs/mise-en-prod
David b22f4e0b74 docs: procedure de mise en production pas a pas
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
2026-09-07 21:40:50 +02:00
..
01-prerequis.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
02-provisioning-hetzner.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
03-durcissement-serveurs.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
04-noeud-donnees.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
05-cluster-k3s.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
06-secrets-sops.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
07-stockage-objet-s3.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
08-dns-tls-cloudflare.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
09-deploiement-application.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
10-cicd-github-actions.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
11-observabilite.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
12-sauvegardes-restauration.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
13-securite-durcissement.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
14-runbook-go-live.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
15-exploitation-incidents.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
16-rgpd-conformite.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00
README.md docs: procedure de mise en production pas a pas 2026-09-07 21:40:50 +02:00

Mise en production — Xpeditis sur Hetzner

Procédure complète, pas à pas, pour ouvrir Xpeditis au public de manière sûre.

Option retenue : Hetzner auto-hébergé (feuille « Hetzner (auto-hébergé) » du fichier Xpeditis_Previsions_Couts.xlsx), k3s sur 2 serveurs, ≈ 66 € HT/mois d'infrastructure.

Tous les fichiers évoqués ici vivent dans infra/prod/.


Lisez ceci en premier

L'analyse du dépôt a mis au jour quatre problèmes qui auraient compromis ou cassé la production. Ils sont traités dans les fichiers livrés, mais deux exigent une action de votre part.

0. Une migration créait un ADMIN au mot de passe public — corrigé

1730000000007-SeedTestUsers insérait admin@xpeditis.com (rôle ADMIN), manager@ et user@, tous avec le mot de passe Password123! — écrit en clair dans le dépôt. Sur une base de production neuve, appliquer les migrations créait donc un administrateur dont les identifiants sont publics. C'était le point le plus grave du parcours.

Trois mécanismes, tous automatiques :

Migration Rôle
1730000000007-SeedTestUsers (modifiée) Ne s'exécute plus si NODE_ENV=production. Les comptes ne sont jamais créés. Dev et preprod gardent les leurs.
1756000000000-NeutralizeSeedAccountsInProduction Filet de sécurité pour toute base où ils existeraient déjà : renommage, hash inauthentifiable, désactivation. Échoue si le nettoyage est incomplet.
1756000000001-BootstrapAdminFromEnv Crée votre administrateur depuis BOOTSTRAP_ADMIN_EMAIL, sans aucun mot de passe stocké : vous définissez le vôtre via « mot de passe oublié ».

Résultat : aucun secret n'existe nulle part — ni dans Git, ni dans le Secret Kubernetes, ni dans l'historique du shell. preflight-check.sh vérifie ensuite en conditions réelles que Password123! est bien refusé sur les trois adresses.

→ 09 — Déploiement applicatif

1. Les secrets de preprod sont dans l'historique Git — action requise

infra/preprod/docker-stack.preprod.yml est versionné et contient en clair : mot de passe PostgreSQL, mot de passe Redis, JWT_SECRET, identifiants MinIO, clé SMTP Brevo, clés Stripe de test, mot de passe administrateur Grafana.

Ils sont dans l'historique Git : les réécrire ne suffirait pas, il faut les considérer comme compromis et les révoquer. La clé Brevo en particulier est un identifiant tiers actif : quiconque a lu le dépôt peut envoyer des e-mails au nom de noreply@xpeditis.com.

→ 13-securite-durcissement.md § Rotation obligatoire

2. L'image frontend ne peut pas être promue depuis la preprod — corrigé

next.config.js fige NEXT_PUBLIC_API_URL au moment du build. L'ancien cd-main.yml re-taguait l'image de preprod vers la production : l'application en production aurait appelé api.preprod.xpeditis.com. Le workflow réécrit reconstruit le frontend avec les URLs de production et vérifie que l'URL de preprod n'est pas présente dans le bundle avant de déployer.

3. Les migrations partaient en concurrence — corrigé

L'image backend lance les migrations à chaque démarrage de pod (scripts/setup/startup.js). Avec deux replicas, deux processus migrent en même temps ; TypeORM ne sérialise pas entre processus, l'un des deux part en CrashLoopBackOff. Un Job Kubernetes (parallélisme 1) applique désormais les migrations avant la mise à jour des images ; startup.js ne fait plus que constater qu'il n'y a rien à migrer. Aucune modification du code applicatif.

Un quatrième point ne bloque pas le lancement mais dégrade une fonctionnalité : les notifications temps réel. Voir 15-exploitation-incidents.md § Points de vigilance.


Chronologie

Quand Étape Durée Document
J-14 Comptes, domaine, outils, décisions 2-3 h 01
J-10 Serveurs, réseau, firewalls (Terraform) 1 h 02
J-10 Durcissement système des deux serveurs 1 h 03
J-9 PostgreSQL + Redis + sauvegardes 2 h 04
J-8 Cluster k3s 1 h 30 05
J-8 Secrets SOPS 1 h 06
J-7 Stockage objet 45 min 07
J-7 DNS, Cloudflare, TLS 1 h 30 08
J-6 Premier déploiement applicatif 2 h 09
J-5 CI/CD automatisée 1 h 30 10
J-4 Supervision et alertes 2 h 11
J-3 Test de restauration réel 2 h 12
J-2 Revue de sécurité, rotation des secrets 3 h 13
J-1 Répétition générale, go / no-go 2 h 14
J0 Ouverture — 14
après Exploitation, incidents, montée en charge — 15
après RGPD et conformité — 16

Temps de travail cumulé : environ 25 heures. Étalez-les : plusieurs étapes comportent des délais incompressibles (propagation DNS, émission de certificat, première sauvegarde complète).


Les documents

# Fichier Contenu
01 Prérequis Comptes, outils, domaine, clés SSH, décisions à trancher
02 Provisioning Hetzner Terraform, serveurs, réseau privé, firewalls, volume
03 Durcissement des serveurs SSH, UFW, fail2ban, auditd, mises à jour automatiques
04 Nœud de données PostgreSQL 15 + TLS, Redis 7, WAL-G, timers de sauvegarde
05 Cluster k3s Installation durcie, Traefik, cert-manager, kubeconfig
06 Secrets SOPS Clé age, chiffrement, application, rotation, sauvegarde de la clé
07 Stockage objet Hetzner Object Storage, buckets, clés cloisonnées
08 DNS, TLS, Cloudflare Enregistrements, proxy, WAF, certificat wildcard
09 Déploiement applicatif Manifests, migrations, premier administrateur, données de référence
10 CI/CD Secrets GitHub, environnement protégé, déploiement automatique
11 Observabilité Loki, Prometheus, Grafana, alertes Discord, supervision externe
12 Sauvegardes et restauration Stratégie 3-2-1, PITR, tests de restauration, RTO/RPO
13 Sécurité Rotation des secrets compromis, checklist, revue externe
14 Runbook de mise en ligne J-1, J0, go/no-go, plan de repli
15 Exploitation et incidents Runbooks, points de vigilance, montée en charge, mises à jour
16 RGPD et conformité Hébergement, sous-traitants, conservation, droits des personnes

Architecture livrée

Internet → Cloudflare (WAF, anti-DDoS, cache)
             │  le firewall Hetzner n'accepte 80/443 que depuis Cloudflare
             ▼
   app-01 · CPX41 · fsn1 · k3s mono-nœud
     Traefik ──► api.xpeditis.com   → backend NestJS ×2 (HPA 2→4)
              ├► app / www / apex   → frontend Next.js ×2
              └► grafana.xpeditis.com (filtré par IP)
     observabilité : Loki · Promtail · Prometheus · Alertmanager · Grafana
             │
             │  réseau privé 10.10.1.0/24, PostgreSQL en TLS obligatoire
             ▼
   db-01 · CPX31 · fsn1 · Docker Compose
     PostgreSQL 15 + WAL-G  ·  Redis 7  ·  postgres-exporter
     volume dédié 50 Go
             │
             ├─► Hetzner Object Storage  (documents applicatifs + WAL-G)
             └─► Hetzner Storage Box     (dumps logiques chiffrés age)

Principes appliqués partout

  1. Tout secret est chiffré dans Git (SOPS + age) ou n'y est pas du tout.
  2. La base n'est jamais joignable depuis Internet. Réseau privé, hostssl uniquement, UFW, aucun enregistrement DNS.
  3. Personne ne contourne Cloudflare.
  4. SSH et l'API Kubernetes ne sont ouverts qu'à vos IP. La CI ouvre une fenêtre de deux minutes pour une seule IP, et la referme quoi qu'il arrive.
  5. Une sauvegarde non restaurée n'est pas une sauvegarde.
  6. Ce qui n'est pas surveillé n'existe pas. Chaque défaillance a une alerte, et le silence des sauvegardes est lui-même surveillé — de l'extérieur.

En cas de problème

Situation Aller directement à
Le site ne répond plus 15 § Le site est hors ligne
Un déploiement a mal tourné 15 § Retour arrière
Perte ou corruption de données 12 § Restauration
Suspicion de compromission 13 § Réponse à incident
Certificat expiré 08 § Dépannage TLS