← cv.ndv3.fr Infra en ligne

Salle des machines

Le labo

Ce que fait vraiment tourner cv.ndv3.fr, ndv3.fr, lr9.ndv3.fr, evenyx.ndv3.fr et le reste : une Freebox, un NAS, neuf conteneurs Docker répartis en plusieurs stacks, et un reverse proxy qui route par nom de domaine — y compris vers une machine qui n'est pas le NAS. Plus deux bots Discord qui ne s'exposent jamais. Cliquez un bloc pour le détail.

De la prise murale au navigateur

Internet Réseau local NAS — réseau Docker (bridge) OVH · DNS Sur le NAS · hors reverse proxy Freebox WAN · NAT DXP4800 PLUS 192.168.1.30 Nginx Proxy Manager cv-arthur nginx:alpine ndv3-home nginx:alpine lr9-test nginx:alpine evenyx-presentation nginx:alpine EVENYX :3000 · hors NAS umami analytics umami-db postgres · interne discord-bot-ndv3 node · discord.js discord-bot-lr9 node · discord.js cv.ndv3.fr ndv3.fr lr9.ndv3.fr evenyx.ndv3.fr sous-domaine privé analytics.ndv3.fr

Clique un bloc du schéma pour afficher son détail.

Quatre incidents, quatre résolutions

INC-01 403 de permissions
▸

Le dashboard NAS lit un status.json généré toutes les minutes par un script cron, monté dans le conteneur en volume Docker. Premier test : 403 Forbidden, alors que ls -la côté NAS affichait rwxrwsrwx+ sur le dossier — permissions larges en apparence.

La cause : le NAS gère l'accès de ses dossiers partagés via des ACL (le + après les droits dans ls -la le signale). Un bind mount Docker ne lit que les bits POSIX classiques, pas les ACL — donc le conteneur voyait les vrais droits sous-jacents, bien plus restrictifs (rwx------), et refusait l'accès à l'utilisateur nginx du conteneur.

Résolu en forçant explicitement chmod 755 sur le dossier et 644 sur le fichier, sans compter sur l'ACL affiché. Le genre de piège qui n'existe que parce qu'on est sur un NAS et pas un serveur Linux classique.

INC-02 Le bot qui ne pouvait pas écrire
▸

Les bots Discord tournent en utilisateur non-root (USER node dans le Dockerfile) et doivent écrire leur état — avertissements de modération, dernier live annoncé, jetons d'API rafraîchis — dans /app/data. Premier réflexe : un bind mount vers ./data sur le partage du NAS. Résultat : refus d'écriture au démarrage, alors que les droits affichés semblaient permissifs.

Même racine que l'INC-01 : les ACL du NAS ne traversent pas le bind mount, et l'utilisateur node du conteneur n'a aucune correspondance côté partage — donc aucun droit réel.

Cette fois le chmod ne suffisait plus, parce que le dossier vit sur un partage entièrement géré par le NAS. Résolu avec un volume Docker nommé (discord-bot-ndv3-data), stocké et administré par le démon Docker lui-même, hors du partage et donc hors des ACL. Les données survivent aux redémarrages comme aux reconstructions d'image, et la sauvegarde se fait avec un conteneur jetable : docker run --rm -v discord-bot-ndv3-data:/data -v $(pwd):/backup alpine tar -cf /backup/bot-data.tar /data.

La leçon : sur un NAS, un bind mount vers un partage est un piège pour tout conteneur non-root. Le volume nommé n'est pas un contournement, c'est le bon outil.

INC-03 docker compose introuvable
▸

Le moteur Docker tourne, docker ps répond — mais docker compose et docker buildx renvoient command not found. Ce sont des plugins CLI, des binaires séparés attendus dans ~/.docker/cli-plugins, et l'interface du NAS n'installe que le client de base.

Contournement pendant la résolution : parler directement au démon via son socket Unix, sans passer par le CLI — curl --unix-socket /var/run/docker.sock pour déclencher la construction de l'image, puis un docker run --env-file .env -v discord-bot-ndv3-data:/app/data … pour démarrer le conteneur. Moins confortable qu'un compose up -d, mais strictement équivalent : Compose n'est qu'une surcouche qui traduit un YAML en appels à cette même API.

Deuxième point de friction sur la même chaîne : le compte utilisé pour déployer n'appartenait pas au groupe docker, donc pas d'accès au socket. Réglé par un usermod -aG docker ciblé plutôt qu'en faisant passer chaque commande par sudo — le principe du moindre privilège appliqué à la bonne granularité.

INC-04 Le PDF fantôme
▸

Le CV s'anime à l'ouverture : chaque bloc part à opacity:0 et remonte via une animation CSS. À l'impression, une règle animation:none désactivait proprement ces animations… et c'était exactement le problème. Sans animation pour l'exécuter, l'état final n'est jamais atteint : le PDF sortait avec le nom, la photo et le sous-titre invisibles, figés à opacity:0.

Deuxième couche : le conteneur principal porte un min-height:100vh. Masquer ses enfants un par un ne l'empêchait pas de réclamer sa hauteur — d'où des pages noires en fin de document, et l'écran de chargement qui se réimprimait sur chacune.

Corrigé en arrêtant de rafistoler la mise en page écran. Le document imprimé est désormais un bloc HTML dédié, masqué à l'écran et révélé uniquement en @media print, avec sa propre grille claire, ses règles break-inside:avoid pour ne jamais couper une expérience en deux, et un QR code vers la version en ligne. Le conteneur animé, lui, est masqué en entier plutôt que sélecteur par sélecteur. La photo est partagée entre les deux vues en recopiant son src au chargement, pour ne pas embarquer deux fois la même image en base64.

Vérifié en rendu réel plutôt qu'à l'œil : Chrome en mode --headless --print-to-pdf à chaque itération, jusqu'à ce que les deux pages sortent correctes.

Ce qui tourne, en résumé

Docker & Compose

Neuf conteneurs, plusieurs stacks, volumes nommés pour les données.

Nginx Proxy Manager

Reverse proxy, routage par domaine, TLS. Six hôtes déclarés.

Let's Encrypt

Certificats TLS, renouvellement automatique.

OVH DNS

Zone du domaine, un enregistrement A par sous-domaine.

nginx:alpine

Sites statiques CV, accueil, LR9 et EVENYX, config custom par service.

Umami + PostgreSQL

Analytics auto-hébergées, sans cookies.

Node.js & discord.js

Deux bots Discord, intégrations Twitch, YouTube et Instagram.

Node.js & Express

EVENYX : billetterie, contrôle d'accès, boutiques et planning d'événement.

Polices auto-hébergées

Aucun appel à Google Fonts : rien ne fuite vers un tiers au chargement.