mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-06-28 10:03:40 +02:00
ef24d85d57
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.
115 lines
4.2 KiB
Markdown
115 lines
4.2 KiB
Markdown
---
|
|
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
|