mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-06-28 01:53:40 +02:00
capitalisation: triage 95_a_capitaliser + création domaine infra
Triage des 27 propositions du buffer de capitalisation (skill capitalisation-triage), avec vérification des doublons contre la base. Intégré dans knowledge/ (23 entrées): - backend: redis (compensation incrBy non-atomique), nestjs (injection cassée sous tsx watch; guard write mode dégradé), async (test rollback pipeline multi-fichiers), contracts (idempotence POST), auth (disclosure comptes soft-deleted), prisma (index partial soft-delete), llm-providers (nouveau: OAuth vs API key, prompt caching). - frontend: tests (garde-fous parking Later), navigation (fichiers non-route sous src/app Expo Router), general (type client vs payload backend), state (fallback catch-all mapping DB→UI). - workflow: story-tracking (statut BMAD vs narratif obsolète). - product: general (nouveau: doc feature store sans UI). - infra: NOUVEAU DOMAINE (traefik, tailscale, docker, docker-networking, reverse-proxy-paths, sidecar tailscale) + 00_INDEX.md. Autres: - 90_debug_et_postmortem.md: post-mortem réseau Docker partagé hors compose. - Rejeté 3 doublons (types enum contracts, getter PrismaService, $transaction). - Buffer 95_a_capitaliser.md purgé et restauré à son état initial. - _projects.conf: MAJ statuts epics + ajout app-rl799.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Infra — Patterns validés — Index
|
||||
|
||||
Patterns d'infrastructure (Docker, reverse proxy, Tailscale, homelab NUC) testés et validés en conditions réelles.
|
||||
|
||||
Avant toute proposition infra, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
|
||||
|
||||
---
|
||||
|
||||
| Fichier | Domaine | Entrées clés |
|
||||
|---------|---------|--------------|
|
||||
| `docker-networking.md` | Bridges Docker, communication container ↔ hôte | Bridges isolés / service hôte injoignable, conteneuriser plutôt que firewall, `network_mode: host`, anti-pattern `host-gateway` |
|
||||
| `reverse-proxy-paths.md` | Servir une app sous un sous-chemin (Traefik) | App consciente du préfixe (stripPrefix ou non), chemins relatifs, app racine, conflits de path multi-apps |
|
||||
| `tailscale.md` | Sidecar Tailscale pour app incompatible subpath | Quand l'utiliser, architecture, squelette compose, `serve.json`, init obligatoire, coûts |
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Infra — Patterns validés : Docker networking
|
||||
domain: infra
|
||||
bucket: patterns
|
||||
tags: [docker, networking, bridge, traefik, host-gateway, firewall]
|
||||
applies_to: [architecture, implementation, debug]
|
||||
severity: medium
|
||||
validated_on: 2026-06-25
|
||||
source_projects: [_Assistant_Cuisine]
|
||||
---
|
||||
|
||||
# Infra — Patterns validés : Docker networking
|
||||
|
||||
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/infra/patterns/README.md` pour l'index complet.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-bridge-isole-service-hote"></a>
|
||||
## Bridges Docker isolés — un container ne peut pas joindre un service hôte par défaut
|
||||
|
||||
### Constat
|
||||
|
||||
Un container connecté à un bridge custom (ex. `traefik-net` en `172.21.0.0/16`) ne peut **pas** atteindre par défaut :
|
||||
|
||||
- l'IP hôte côté `docker0` (`172.17.0.1`)
|
||||
- l'IP hôte sur d'autres interfaces (ex. IP Tailscale `100.x.x.x`)
|
||||
- `host.docker.internal` (qui résout vers `172.17.0.1`, donc même blocage)
|
||||
|
||||
Même quand l'hôte écoute bien sur `0.0.0.0:PORT`, l'appel depuis le container part en timeout. Cause : sous Docker 29 + iptables/nftables strict, les bridges sont isolés entre eux et le forward bridge → host est bloqué par défaut.
|
||||
|
||||
**Conséquence** : faire pointer un router Traefik vers `host.docker.internal:PORT` pour atteindre un service systemd hôte ne fonctionne pas en bridge mode.
|
||||
|
||||
### Bonnes pratiques / solutions (par ordre de propreté)
|
||||
|
||||
**Solution 1 (préférée) — Conteneuriser le service hôte**
|
||||
|
||||
Mettre le service dans un container connecté au même bridge (`traefik-net`). La communication passe par le DNS Docker interne (`servicename:port`). Aucune complexité réseau, fonctionne nativement. C'est presque toujours possible (ex. code-server via l'image `lscr.io/linuxserver/code-server`).
|
||||
|
||||
**Solution 2 — Traefik en `network_mode: host`**
|
||||
|
||||
Traefik abandonne son bridge custom et écoute directement sur les interfaces hôte. Il atteint tous les services hôte trivialement, MAIS perd l'accès aux containers via bridge : il faut alors leur publier des ports hôte, ce qui défait l'intérêt du reverse proxy interne. Réservé aux setups simples où Traefik ne sert que des services hôte.
|
||||
|
||||
**Solution 3 — Ouvrir manuellement le firewall**
|
||||
|
||||
```bash
|
||||
sudo iptables -I DOCKER-USER -s 172.21.0.0/16 -d 172.21.0.1 -p tcp --dport <PORT> -j ACCEPT
|
||||
```
|
||||
|
||||
Fragile : règle volatile, à refaire au reboot ou à l'upgrade Docker. Si on prend ce chemin, documenter et provisionner via une unit systemd.
|
||||
|
||||
### Anti-pattern : `extra_hosts: host.docker.internal:host-gateway`
|
||||
|
||||
Cette ligne déclare seulement un alias DNS dans le `/etc/hosts` du container ; elle ne crée **aucune route réseau**. Si le firewall Docker bloque, l'alias ne sert à rien : le container résout `host.docker.internal` mais le SYN reste bloqué.
|
||||
|
||||
**Règle** : avant de partir sur la Solution 2 ou 3, toujours vérifier qu'on ne peut pas conteneuriser le service. C'est presque toujours possible et toujours plus propre.
|
||||
|
||||
- Contexte technique : Docker 29 / Traefik / NUC — _Assistant_Cuisine 04-05-2026
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Infra — Patterns validés : Reverse proxy & sous-chemins
|
||||
domain: infra
|
||||
bucket: patterns
|
||||
tags: [traefik, reverse-proxy, pathprefix, stripprefix, subpath, spa]
|
||||
applies_to: [architecture, implementation, debug]
|
||||
severity: medium
|
||||
validated_on: 2026-06-25
|
||||
source_projects: [_Assistant_Cuisine]
|
||||
---
|
||||
|
||||
# Infra — Patterns validés : Reverse proxy & sous-chemins
|
||||
|
||||
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/infra/patterns/README.md` pour l'index complet.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-app-sous-chemin-reverse-proxy"></a>
|
||||
## Servir une app sous un sous-chemin via reverse proxy
|
||||
|
||||
Le pattern qui fonctionne dépend de la façon dont l'app gère ses URLs internes (assets, API, redirects). Trois cas.
|
||||
|
||||
### Cas 1 — App nativement consciente du préfixe
|
||||
|
||||
L'app prend une option de base path et émet ses URLs internes préfixées. Côté Traefik, lire la doc de l'app pour savoir si un `stripPrefix` est requis — il n'y a **pas de règle universelle** :
|
||||
|
||||
- **App qui reçoit le path complet et sait quoi en faire** (ex. Cooklang `cook server --url-prefix /cuisine`) : `PathPrefix(/cuisine)` **sans** `stripPrefix`.
|
||||
- **App qui veut recevoir `/` mais émet ses assets sous le préfixe** (ex. Portainer `--base-url=/portainer`) : `PathPrefix(/portainer)` **avec** `stripPrefix /portainer`.
|
||||
|
||||
### Cas 2 — App qui sert uniquement des chemins relatifs
|
||||
|
||||
Si l'app utilise des URLs relatives partout (`<a href="./xxx">`, `<script src="./xxx">`, redirects relatifs aussi), elle fonctionne sous n'importe quel sous-chemin avec un simple `stripPrefix` côté Traefik. L'app ne sait pas où elle est, mais le navigateur résout correctement les URLs relatives (cas observé sur code-server).
|
||||
|
||||
⚠️ Vérifier qu'**aucune URL absolue** ne traîne dans le HTML/JS. Un `href="/static/..."` (au lieu de `href="./static/..."` ou `href="static/..."`) casse le rendu.
|
||||
|
||||
### Cas 3 — App qui veut être à la racine
|
||||
|
||||
Beaucoup de SPA modernes (Homepage, Vite/Next statiques) hardcodent `href="/_next/..."` etc. Le path prefix casse alors les assets. Solutions :
|
||||
|
||||
- Sous-domaine dédié (`homepage.example.com`) plutôt que sous-chemin.
|
||||
- Sinon, plugin de réécriture de body (ex. `traefik/plugin-rewritebody`) qui patche le HTML à la volée.
|
||||
- Si l'app est incompatible subpath ET doit rester derrière Tailscale : sidecar Tailscale dédié (voir `knowledge/infra/patterns/tailscale.md`).
|
||||
|
||||
**Règle** : tester en priorité avec un simple `PathPrefix + stripPrefix`. Si les assets cassent, lire la doc de l'app pour une option de base path. Si rien d'officiel, basculer sur sous-domaine (ou sidecar Tailscale).
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-conflit-path-multi-apps"></a>
|
||||
## Conflit de path entre apps multiples — anticipation
|
||||
|
||||
Quand plusieurs apps cohabitent sur le même domaine :
|
||||
|
||||
- **Avant** d'ajouter une app, vérifier que son préfixe ne va pas matcher des routes d'une autre app — voir le cas `/api` dans `knowledge/infra/risques/traefik.md`.
|
||||
- Préférer des préfixes longs et distinctifs (`/cuisine`, `/portainer`, `/code`) plutôt que génériques (`/app`, `/admin`, `/dashboard`).
|
||||
- Documenter dans le compose Traefik le mapping path → service pour garder une vue d'ensemble.
|
||||
|
||||
- Contexte technique : Traefik v3 / NUC — _Assistant_Cuisine 04-05-2026
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: Infra — Patterns validés : Sidecar Tailscale
|
||||
domain: infra
|
||||
bucket: patterns
|
||||
tags: [tailscale, sidecar, docker, subpath, magicdns, tls, spa]
|
||||
applies_to: [architecture, implementation]
|
||||
severity: medium
|
||||
validated_on: 2026-06-25
|
||||
source_projects: [apps/stirling-pdf]
|
||||
---
|
||||
|
||||
# Infra — Patterns validés : Sidecar Tailscale
|
||||
|
||||
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/infra/patterns/README.md` pour l'index complet.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-sidecar-tailscale-subpath"></a>
|
||||
## Sidecar Tailscale pour app incompatible avec subpath
|
||||
|
||||
Quand une app refuse d'être servie sous un sous-chemin (assets et fetch API hardcodés sur `/`) et qu'on veut la garder derrière Tailscale, on lui attache un **sidecar `tailscale/tailscale`** qui rejoint le tailnet avec son propre hostname et termine le TLS via un cert MagicDNS.
|
||||
|
||||
### Quand l'utiliser
|
||||
|
||||
- L'app expose une SPA Vite/Next/CRA avec assets hardcodés sur `/`.
|
||||
- L'app fait des fetch API en chemin absolu (`/api/...`) sans respecter un base path.
|
||||
- Patcher l'upstream ou injecter un `<base href>` est non trivial / fragile.
|
||||
- **Critère de décision** : si la première page charge mais que les assets JS retournent 404 derrière Traefik, c'est ce cas (cf. bug upstream Stirling-PDF #5072).
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
tailnet
|
||||
│
|
||||
▼ https://<app>.<tailnet>.ts.net (cert MagicDNS auto)
|
||||
[container <app>-ts (sidecar tailscale)]
|
||||
│ network: <app>-internal (bridge dédié)
|
||||
▼
|
||||
[container <app>]:<port>
|
||||
```
|
||||
|
||||
- Le sidecar **rejoint le tailnet** comme un nœud à part (compte dans le quota devices).
|
||||
- Cert TLS délivré automatiquement par `tailscale serve` via `serve.json`.
|
||||
- L'app reste servie sur `/` côté interne ; **Traefik n'est pas dans la boucle**.
|
||||
- Réseau Docker dédié, jamais branché sur `traefik-net`.
|
||||
|
||||
### Squelette docker-compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
myapp:
|
||||
image: monimage:tag
|
||||
networks: [app_internal]
|
||||
# PAS d'exposition de ports sur l'hôte
|
||||
# PAS de labels traefik.*
|
||||
|
||||
tailscale:
|
||||
image: tailscale/tailscale:v1.98.3
|
||||
container_name: myapp-ts
|
||||
# ⚠ PAS de `hostname: myapp` ici (cf. risque collision DNS Docker)
|
||||
environment:
|
||||
TS_AUTHKEY: ${MYAPP_TS_AUTHKEY}
|
||||
TS_HOSTNAME: myapp
|
||||
TS_STATE_DIR: /var/lib/tailscale
|
||||
TS_SERVE_CONFIG: /config/serve.json
|
||||
TS_USERSPACE: "false"
|
||||
volumes:
|
||||
- /srv/docker-data/apps/myapp/tailscale/state:/var/lib/tailscale
|
||||
- /srv/docker-data/apps/myapp/tailscale/serve:/config:ro
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
cap_add: [NET_ADMIN, SYS_MODULE]
|
||||
networks: [app_internal]
|
||||
labels:
|
||||
# Auto-discovery homepage : la tile pointe vers le FQDN tailnet, pas vers Traefik
|
||||
- "homepage.group=Apps"
|
||||
- "homepage.name=MyApp"
|
||||
- "homepage.href=https://myapp.<tailnet>.ts.net/"
|
||||
|
||||
networks:
|
||||
app_internal:
|
||||
name: myapp-internal
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
`serve.json` :
|
||||
|
||||
```json
|
||||
{
|
||||
"TCP": { "443": { "HTTPS": true } },
|
||||
"Web": {
|
||||
"myapp.<tailnet>.ts.net:443": {
|
||||
"Handlers": { "/": { "Proxy": "http://myapp:<port-interne>" } }
|
||||
}
|
||||
},
|
||||
"AllowFunnel": { "myapp.<tailnet>.ts.net:443": false }
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠ Ne pas mettre `hostname: <app>` sur le sidecar si le service voisin s'appelle déjà `<app>` : collision DNS Docker non déterministe. `TS_HOSTNAME` fixe le nom côté tailnet et est décorrélé du DNS Docker interne. Voir `knowledge/infra/risques/docker.md`.
|
||||
|
||||
### Init obligatoire
|
||||
|
||||
1. Générer une **auth key Tailscale Reusable, non-Ephemeral** dans l'admin console.
|
||||
2. Stocker le state dans un volume **persistant** (sinon ré-enrôlement à chaque restart et pollution du tailnet).
|
||||
3. Si le tailnet a "Machine approval" activé : approuver le nœud manuellement après le premier `up`.
|
||||
4. **Disable key expiry** sur le nœud après enrôlement (sinon il faut refournir une authkey à l'expiration, par défaut 90 j).
|
||||
|
||||
### Coût du pattern
|
||||
|
||||
- Un nœud tailnet par app (limite gratuite : 100 devices).
|
||||
- Pas de middlewares Traefik partagés (rewriteBody pour `lang="fr"`, basic auth, etc.) → à accepter, ou patcher l'upstream.
|
||||
- Double restart si l'app upstream change : le sidecar continue à serve même si l'app est down → 502 propre, pas de cascade.
|
||||
|
||||
- Contexte technique : Docker / Tailscale / Spring Boot + Vite — apps/stirling-pdf 27-05-2026
|
||||
Reference in New Issue
Block a user