Publier un nouveau service sur *.savethemall.eu¶
Procédure complète pour exposer un service sur un nom public, protégé par Authentik. Compter une trentaine de minutes, dont l'essentiel en attente de propagation DNS.
Dans tout ce qui suit, remplacer monservice par le sous-domaine visé et
monservice:8080 par le nom de service Compose et son port.
Ce que la procédure suppose acquis
- Le conteneur tourne et répond en HTTP sur
infrastructure_network. - Le contrôle : depuis vm-docker,
docker exec caddy wget -qO- http://monservice:8080/ | head -5. Si cette commande ne rend rien, s'arrêter ici : rien de ce qui suit ne compensera un upstream muet.
L'ordre, et pourquoi il n'est pas négociable¶
flowchart TB
A["1 · Record A chez OVH"]
B["2 · Certificat acme.sh<br/>validation DNS-01"]
C["3 · Provider + application<br/>Authentik"]
D["4 · Bloc Caddy vm-docker"]
E["5 · Bloc Caddy nas-caddy"]
F["6 · Secrets, s'il y en a"]
G["7 · decisions.md, commit"]
A -->|"sans le record,<br/>la validation DNS-01 échoue"| B
B -->|"sans le cert, `caddy validate`<br/>refuse et le reload COUPE<br/>les 76 autres sites"| D
C -->|"l'app doit exister,<br/>sinon 404 au lieu de 302"| D
D -->|"le cert doit être copié<br/>sur le NAS"| E
E --> G
F --> G
B -.->|"rsync quotidien<br/>ou déclenché à la main"| E
classDef risk stroke-width:3px
class D risk
La seule étape qui peut casser autre chose est la 4 : le Caddy de vm-docker porte
76 vhosts, et un rechargement sur une configuration invalide les couche tous. D'où
caddy validate avant tout restart, et le certificat avant le bloc.
1. Enregistrement DNS chez OVH¶
Toujours en premier. acme.sh valide en DNS-01 : sans l'enregistrement, l'émission du certificat échoue, et l'échec ressemble à un problème d'identifiants.
ssh -p <port-ssh> <user>@vm-docker \
"sudo /docker-mounts/homelab-vault/00-infra/scripts/ovh-record.sh \
add savethemall.eu A monservice <IP publique> --apply"
Contrôle
Surtout pas le résolveur local : le DNS interne répond déjà pour ce nom, par joker, et donnerait une fausse confirmation.
Et pas non plus les serveurs autoritatifs d'OVH — ns.ovh.net et dns1.ovh.net
ne répondent pas aux requêtes directes depuis ce parc (mesuré le 2026-08-29
depuis vm-claude et depuis le VPS : communications error … timed out). Un
résolveur public a déjà fait le travail.
2. Certificat TLS¶
Ajouter le domaine à la liste d'émission — c'est elle qui sera relue au prochain déploiement complet — puis émettre le certificat seul.
# 1. inscrire le domaine dans la liste
ssh -p <port-ssh> <user>@vm-docker \
"grep -q 'monservice.savethemall.eu' /opt/docker/acme/issue-all.sh || \
sed -i 's|^)| monservice.savethemall.eu\n)|' /opt/docker/acme/issue-all.sh"
# 2. émettre ce seul certificat
ssh -p <port-ssh> <user>@vm-docker \
"docker exec acme acme.sh --issue --dns dns_ovh -d monservice.savethemall.eu \
--cert-file /certs/monservice.savethemall.eu/cert.pem \
--key-file /certs/monservice.savethemall.eu/privkey.pem \
--fullchain-file /certs/monservice.savethemall.eu/fullchain.pem \
--reloadcmd \"curl -sf -X POST http://127.0.0.1:2019/load \
-H 'Content-Type: text/caddyfile' \
--data-binary @/etc/caddy/Caddyfile\""
--dnssleep est obligatoire ici, et la raison n'est pas chez OVH
Sans lui, l'émission échoue systématiquement sur :
acme.sh vérifie son enregistrement TXT par DoH sur cloudflare-dns.com, et
AdGuard résout ce nom vers 0.0.0.0 : le filtrage anti-contournement DNS
de la maison bloque son propre outil d'émission. Le TXT est pourtant bien posé
chez OVH, puis proprement retiré — d'où un échec qui ressemble à un problème
d'identifiants ou de propagation.
Le contrôle qui tranche en une commande :
docker exec acme sh -c 'nslookup cloudflare-dns.com | tail -3'
# Address: 0.0.0.0 → c'est AdGuard, pas OVH
--dnssleep remplace la vérification par une attente fixe. C'est le
contournement ; le correctif est d'autoriser cloudflare-dns.com dans AdGuard.
Contrôle
Le NAS reçoit sa copie au rsync quotidien. Pour ne pas attendre, la déclencher
à la main avant l'étape 5.
3. Application et fournisseur dans Authentik¶
Dans l'interface d'administration :
- Applications → Providers → Create → Proxy Provider
- Mode : Forward auth (single application) — pas Proxy, pas Domain level.
- External host :
https://monservice.savethemall.eu - Flux d'autorisation : celui du parc (consentement implicite).
- Applications → Create
- Slug :
monservice - Provider : celui qui vient d'être créé
- Icon : chercher le logo officiel avant de créer l'application. Par ordre :
le dépôt GitHub du projet, le
/favicon.svgdu site officiel,https://cdn.simpleicons.org/<nom>, puis le cataloguehttps://cdn.jsdelivr.net/gh/selfhst/icons/svg/<nom>.svg.
- Slug :
- Applications → Outposts →
authentik Embedded Outpost→ Edit → Applications- Ajouter l'application à la main.
L'outpost embarqué ne récupère pas l'application tout seul
C'est l'oubli le plus fréquent de cette procédure. Sans cet ajout explicite, tout est correctement configuré côté Authentik, Caddy renvoie bien vers le flux de connexion, et la connexion échoue quand même — sans message qui désigne la cause.
4. Bloc Caddy sur vm-docker¶
Éditer 10-stacks/caddy/Caddyfile dans le vault, puis synchroniser. Le
fichier du vault n'est pas un lien symbolique : les deux copies sont réelles et
divergent si on oublie.
monservice.savethemall.eu {
import tls_cert monservice.savethemall.eu
# Endpoints de l'outpost embarqué (sign_out, start, callback) → Authentik
handle /outpost.goauthentik.io/* {
reverse_proxy authentik-server:9000
}
handle {
import authentik
reverse_proxy monservice:8080
}
}
L'ordre des deux handle n'est pas décoratif : voir
forward_auth passe avant handle.
Synchronisation, obligatoire après toute édition :
ssh -p <port-ssh> <user>@vm-docker \
"cp /docker-mounts/homelab-vault/10-stacks/caddy/Caddyfile /opt/docker/caddy/Caddyfile && \
docker compose -f /opt/docker/caddy/docker-compose.yml restart"
Contrôle — en LAN, où le DNS pointe déjà sur vm-docker
curl -s -o /dev/null -w '%{http_code}\n' https://monservice.savethemall.eu/
# 302 vers auth.savethemall.eu → le mur est en place
curl -s -o /dev/null -w '%{http_code}\n' \
https://monservice.savethemall.eu/outpost.goauthentik.io/zzz
# 404 → la route de l'outpost existe (302 ici = le handle est mal placé)
5. Bloc Caddy sur nas-caddy — c'est lui qui ouvre le WAN¶
Tant que cette étape n'est pas faite, le service répond en LAN et en tailnet, et reste invisible depuis Internet. C'est le NAS qui est l'edge.
Fichier : /volume3/docker/nas-caddy/Caddyfile sur le NAS.
Rien d'autre : l'authentification et le routage restent en aval, sur vm-docker.
Le snippet (to_vmdocker) porte déjà header_up Host {host} — sans lui, l'amont
recevrait Host: vm-docker.
Variante : garder le service hors du WAN
Pour un service qui doit rester interne, ajouter import lan_only au bloc
plutôt que de ne pas le déclarer. Ici, remote_ip est bien le client réel :
nas-caddy est l'edge, il n'y a pas de proxy en amont pour brouiller la
source.
Contrôle — le vrai chemin public
curl -s -o /dev/null -w '%{http_code}\n' \
--resolve monservice.savethemall.eu:443:<IP publique> \
https://monservice.savethemall.eu/
Sans --resolve, la commande repasse par le DNS interne et ne teste rien du
chemin WAN.
6. Secrets, s'il y en a¶
- Les valeurs vivent dans
/opt/docker/monservice/.envsur l'hôte, jamais dans le vault en clair, jamais dans le fichier Compose. - La version chiffrée va dans
00-infra/secrets/vm-docker/monservice.enc.env(SOPS + age). L'arbre est indexé par hôte : trois noms de stack existent sur deux hôtes ou plus, et sans cette qualification le second écrase le premier — sans bruit. - Seule la clé publique age est nécessaire pour chiffrer. Aucune clé privée n'a besoin d'exister sur les autres hôtes.
- Tout jeton d'API installé se range dans Vaultwarden dans la foulée, sous la
convention
<hôte>/<stack>·<NOM_DE_VARIABLE>.
Le ciphertext poussé est permanent
Ces fichiers partent aussi sur GitHub. Le dépôt est privé, mais ce qui a été poussé y reste : une rotation de secret ne rend pas illisible le chiffré déjà publié. Rotation veut dire rotation en amont, pas simplement réécriture du fichier.
Jamais de secret dans une URL
La trace d'erreur journalise l'URL résolue, jeton compris. De même, Caddy
n'expurge automatiquement que trois en-têtes — Authorization, Cookie,
Set-Cookie. Un en-tête maison (X-Mon-Token) part en clair dans
access.log, qui est lu par CrowdSec. Tout nouvel en-tête porteur d'un secret
doit être ajouté au filtre du journal en même temps qu'il est créé.
7. Écrire ce qui vient d'être fait¶
- Entrée datée dans
10-stacks/monservice/decisions.md: raison, et comment revenir en arrière. - Mise à jour de la table maîtresse des stacks.
- Commit du vault et poussée sur les deux dépôts distants.
Récapitulatif¶
- Enregistrement A chez OVH, vérifié sur le serveur autoritatif
- Certificat émis, domaine ajouté à la liste d'émission
- Fournisseur Proxy Authentik en mode forward auth (single application)
- Application créée, avec son logo
- Application ajoutée à la main à l'outpost embarqué
- Bloc Caddy vm-docker, dans le vault et synchronisé sur l'hôte
- Sonde
/outpost.goauthentik.io/zzz→ 404 - Bloc Caddy nas-caddy, certificat copié sur le NAS
- Contrôle WAN avec
--resolve - Secrets chiffrés dans le vault, jeton rangé dans le coffre
-
decisions.md, commit, double poussée
Dernière vérification de cette page : 2026-09-08