Aller au contenu principal

Déploiement — Coolify (Docker Compose) + Cloudflare

Cible : un serveur avec Coolify (proxy Traefik), le domaine fs0ciety.org chez Cloudflare, l'application servie sur https://agenda.fs0ciety.org. Fichiers concernés : docker-compose.prod.yml, apps/api/Dockerfile, apps/web/Dockerfile, .env.prod.example.

1. Vue d'ensemble​

Navigateur / Android
│ HTTPS
▼
Cloudflare (DNS proxifié, TLS, IP client → CF-Connecting-IP)
│ HTTPS (Full strict)
▼
Serveur Coolify ── Traefik :443 ── agenda.fs0ciety.org
│
▼
web (Next.js :3000) ── /v1/*, /healthz ──▶ api (NestJS :4000)
│
┌──────────────┼──────────────┐
▼ ▼ ▼
postgres (:5432) redis (:6379) Google Calendar API
ServiceExpositionRôle
webseul service public (domaine Coolify)Pages + proxy same-origin /v1/* → API
apiinterne (réseau Docker)API REST ; applique les migrations au démarrage
postgresinterneDonnées (volume agenda_postgres_data)
backupinterneSauvegarde nuitière vérifiée par restauration (agenda_postgres_backups, copie R2)
redisinterneFile BullMQ de la synchro Google Calendar (volume agenda_redis_data)

Redis ne contient que des déclencheurs : l'état de synchronisation vit dans PostgreSQL. Perdre Redis (ou son volume) ne perd aucune donnée ; il n'est donc pas à sauvegarder.

Pourquoi un seul domaine public ?​

Option+−
A. Web public, API interne (proxy /v1/*) — retenueCookies first-party (SameSite=Lax suffit), pas de CORS, une seule entrée à protéger, un seul certificat, Android et OAuth Google utilisent le même domaineUn saut réseau interne de plus (négligeable)
B. agenda-api.fs0ciety.org public en plusAPI joignable directementSurface d'attaque doublée, CORS + cookies cross-subdomain à régler

Si un domaine d'API public devient nécessaire plus tard, utiliser un sous-domaine à un seul niveau (agenda-api.fs0ciety.org, pas api.agenda.fs0ciety.org) : le certificat universel gratuit de Cloudflare ne couvre que *.fs0ciety.org, pas les niveaux plus profonds.

2. Prérequis​

  • Serveur Coolify opérationnel (Traefik actif, ports 80/443 ouverts), accès au dépôt GitHub fs0ciety7000/agenda via la GitHub App Coolify (ou une deploy key).
  • Zone fs0ciety.org gérée par Cloudflare.
  • ≥ 2 Go de RAM libres pendant le build (le build Next.js est le plus gourmand), ~3 Go de disque pour les images.

3. Cloudflare​

3.1 DNS​

TypeNomContenuProxy
AagendaIP publique du serveur CoolifyDNS only (nuage gris) au premier déploiement, puis Proxied (nuage orange)

Pourquoi gris d'abord : Traefik obtient le certificat Let's Encrypt par défi HTTP‑01. En mode proxifié + « Full (strict) », Cloudflare refuserait le certificat auto-signé servi par Traefik tant que le vrai n'est pas émis (erreur 526). Une fois https://agenda.fs0ciety.org servi avec un certificat Let's Encrypt valide, passer le nuage en orange. Les renouvellements passent ensuite sans problème à travers Cloudflare.

3.2 Réglages de la zone​

RéglageValeurRaison
SSL/TLS → modeFull (strict)Chiffrement de bout en bout, certificat d'origine vérifié
Always Use HTTPSOnCookies Secure
Minimum TLS1.2
Rocket LoaderOffRéécrit les scripts : casse l'hydratation React
Email Address ObfuscationOff (ou règle de configuration sur ce hostname)Modifie le HTML : erreurs d'hydratation
CachePar défaut (le HTML et le JSON ne sont pas mis en cache) + Cache Rule « Bypass » pour agenda.fs0ciety.org/v1/*Ne jamais servir une réponse d'API d'un autre utilisateur

3.3 IP réelle du client​

Cloudflare pose CF-Connecting-IP ; l'API l'utilise pour le rate limiting (CLIENT_IP_HEADER=cf-connecting-ip). Sans cela, l'API ne verrait que l'IP du conteneur web et tous les utilisateurs partageraient la même limite.

Cet en-tête n'est fiable que si le trafic passe par Cloudflare. Recommandé : pare-feu du serveur limitant 80/443 aux plages IP Cloudflare (après l'émission du premier certificat), en gardant le port d'administration de Coolify accessible depuis votre IP.

4. Coolify​

4.1 Créer la ressource​

  1. Projects → + New → Resource → Private Repository (GitHub App) → fs0ciety7000/agenda.
  2. Branche : main (ou la branche de déploiement choisie).
  3. Build Pack : Docker Compose.
  4. Docker Compose Location : /docker-compose.prod.yml (le docker-compose.yml de la racine sert au développement local uniquement).
  5. Enregistrer : Coolify lit le compose et liste les services web, api, postgres.

4.2 Domaine​

Dans la ressource → service web → Domains :

https://agenda.fs0ciety.org:3000

Le suffixe :3000 indique à Traefik le port du conteneur. L'URL publique reste https://agenda.fs0ciety.org. Ne mettre aucun domaine sur api ni postgres.

Service docs (site de documentation, facultatif) → Domains : https://agenda-docs.fs0ciety.org:80 — voir §13. Aucun label Traefik dans le compose : Coolify génère tout le routage.

4.3 Variables d'environnement​

Coolify détecte les ${VAR} du compose et les affiche dans Environment Variables. Celles marquées :? sont obligatoires : le déploiement échoue avec un message clair si elles manquent. Modèle complet : .env.prod.example.

VariableValeurGénération
WEB_ORIGINhttps://agenda.fs0ciety.org—
POSTGRES_PASSWORDsecretopenssl rand -hex 24
JWT_SECRETsecret ≥ 32 caractèresopenssl rand -base64 48
TOKEN_ENCRYPTION_KEY32 octets en base64openssl rand -base64 32
CLIENT_IP_HEADERcf-connecting-ipdéfaut
REGISTRATION_ENABLEDtrue, puis false après vos deux inscriptionscf. §5
AUTH_RATE_LIMIT10défaut (connexion, inscription, refresh)
GLOBAL_RATE_LIMIT600défaut (toute l'API, par IP)
POSTGRES_USER / POSTGRES_DBagendadéfaut
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETactive Google Calendar et « Continuer avec Google »cf. §9
SMTP_*, EMAIL_FROMfacultatif : active « Mot de passe oublié »cf. §8
GITHUB_RELEASES_TOKENvide si le dépôt est public ; sinon jeton GitHub lecture seule (Contents)cf. android.md §4
SENTRY_DSNfacultatif : suivi des erreurs (§10)DSN Sentry / GlitchTip
FCM_SERVICE_ACCOUNTfacultatif : notifications instantanées AndroidJSON du compte de service Firebase (docs/android.md §4.1)
INBOUND_EMAIL_ADDRESS, RESEND_WEBHOOK_SECRET, RESEND_API_KEYfacultatif : tâches par e-mail (réception par Resend)cf. email-to-task.md
ADMIN_EMAILSfacultatif : accès à la page d'administrationadresses e-mail séparées par des virgules ; reçoivent aussi les alertes de surveillance
METRICS_TOKENfacultatif : GET /metrics (Prometheus)secret d'au moins 16 caractères, cf. monitoring.md
DOCS_URLfacultatif : adresse du site de documentation (build)https://agenda-docs.fs0ciety.org par défaut, cf. §13
BACKUP_HEARTBEAT_URLfacultatif : battement de cœur des sauvegardesURL « Push » d'Uptime Kuma / Healthchecks.io
WEB_PUSH_PUBLIC_KEY, WEB_PUSH_PRIVATE_KEYfacultatif : notifications du site (navigateur)paire de clés VAPID, voir ci-dessous
WEB_PUSH_SUBJECTfacultatifcontact pour les services de push (mailto:… ou https://…) ; défaut : WEB_ORIGIN

Clés VAPID (notifications du site). À générer une seule fois : Coolify → service api → Terminal → node -e "console.log(require('web-push').generateVAPIDKeys())", puis copier publicKey dans WEB_PUSH_PUBLIC_KEY et privateKey dans WEB_PUSH_PRIVATE_KEY (secrète, sans Available at Buildtime) et redéployer. Chaque personne active ensuite les notifications dans Réglages → Notifications → « Activer sur ce navigateur ». Changer les clés oblige chacun à les réactiver.

REDIS_URL est fixée par le compose (redis://redis:6379) : rien à définir.

⚠️ TOKEN_ENCRYPTION_KEY ne doit jamais changer une fois Google Calendar connecté : les tokens chiffrés deviendraient illisibles (il faudrait reconnecter Google). Conservez-la dans un gestionnaire de mots de passe. Changer JWT_SECRET déconnecte simplement tout le monde.

Cocher Is Build Variable? n'est nécessaire pour aucune de ces variables : la seule valeur de build (API_URL=http://api:4000) est fixée dans le compose.

4.4 Déployer​

Deploy. Séquence attendue (logs Coolify) :

  1. Build des images api et web (≈ 2–4 min au premier build).
  2. postgres devient healthy.
  3. api démarre : prisma migrate deploy applique les migrations (« All migrations have been successfully applied »), puis healthy via /health/ready.
  4. web démarre après l'API, healthy via /login.

Vérifications :

curl -s https://agenda.fs0ciety.org/healthz # {"status":"ok","checks":{"database":"ok"}}
curl -sI https://agenda.fs0ciety.org/ | head -3 # 307 → /login

4.5 Déploiements suivants​

  • Activer Auto Deploy (webhook GitHub) : chaque push sur la branche redéploie.
  • Les migrations sont appliquées automatiquement au démarrage de l'API (migrate deploy n'applique que des migrations versionnées, jamais de reset).
  • Rollback : redéployer un commit précédent depuis Coolify. Les migrations sont forward only : une migration destructive (suppression de colonne) doit toujours être livrée en deux temps (code compatible d'abord, suppression ensuite) pour qu'un rollback de code reste sûr.

5. Premier démarrage​

  1. Ouvrir https://agenda.fs0ciety.org → Créer un compte (Nicolas, par exemple).
  2. Onboarding : créer le foyer « G & N » → Générer un lien d'invitation → l'envoyer à Grace.
  3. Grace ouvre le lien → crée son compte → Rejoindre.
  4. Dans Coolify : REGISTRATION_ENABLED=false → Redeploy. Les comptes existants continuent de se connecter ; toute nouvelle inscription reçoit « Les inscriptions sont fermées ».

6. Sauvegardes​

Le service backup du compose s'en charge, sans configuration dans Coolify :

  • chaque nuit (BACKUP_HOUR, UTC, défaut 2 h 15) et au premier démarrage : pg_dump compressé dans le volume agenda_postgres_backups, conservé 14 jours (BACKUP_RETENTION_DAYS) ;
  • chaque dump est restauré dans une base temporaire et contrôlé (comptes, foyers, occurrences) : un dump inutilisable est signalé le jour même (ÉCHEC dans les logs) ;
  • copie hors serveur si configurée (ci-dessous), conservée 30 jours ;
  • le conteneur passe unhealthy sans sauvegarde réussie depuis 26 h : activer les notifications Coolify (Settings → Notifications, e-mail ou Discord) pour être prévenu.

Logs : Coolify → service backup → Logs, ligne backup: OK …/agenda-AAAA-MM-JJTHHMM.dump (…) — restauration vérifiée : N comptes, ….

6.1 Copie hors serveur (recommandé) — Cloudflare R2​

Un disque qui lâche ou un serveur perdu emporte aussi les dumps locaux.

  1. Cloudflare → R2 → Create bucket agenda-backups (région automatique, privé). Le plan gratuit (10 Go) suffit largement (un dump fait quelques centaines de Ko).
  2. R2 → Manage API tokens → Create API token : permission Object Read & Write, limité au bucket agenda-backups. Noter Access Key ID, Secret Access Key et l'endpoint (https://<id-compte>.r2.cloudflarestorage.com).
  3. Dans Coolify (variables de la ressource), puis redéployer :
VariableValeur
BACKUP_OFFSITE_REMOTEoffsite:agenda-backups
BACKUP_S3_ENDPOINThttps://<id-compte>.r2.cloudflarestorage.com
BACKUP_S3_ACCESS_KEY_IDclé du jeton
BACKUP_S3_SECRET_ACCESS_KEYsecret du jeton

Autre stockage S3 (Backblaze B2, Scaleway…) : BACKUP_S3_PROVIDER (valeur rclone) + mêmes variables. Vérifier dans les logs : copie hors serveur : offsite:agenda-backups/agenda-….dump.

6.2 Sauvegarde immédiate​

Site → Réglages → Administration → « Sauvegarder maintenant » (compte listé dans ADMIN_EMAILS) : le service backup la prend en charge dans la minute ; l'historique (nuit, démarrage, manuelles, avec taille, contrôle de restauration et copie hors serveur) s'affiche au même endroit. Sans accès au site : Coolify → service backup → Terminal : backup.sh once.

6.3 Restauration​

# Coolify → service backup → Terminal
ls /backups
backup.sh restore /backups/agenda-AAAA-MM-JJTHHMM.dump

Puis redémarrer le service api. Depuis R2 : télécharger le fichier (rclone copy offsite:agenda-backups/agenda-….dump /backups/) puis même commande.

7. Android​

L'app parle au même domaine que le web (le proxy /v1/* est public) : rien à déployer côté serveur. APK de recette publié par la CI, ou APK signé avec votre clé : voir android.md §4.

8. Emails (mot de passe oublié)​

L'API envoie ses emails en SMTP standard : n'importe quel fournisseur convient, sans changer le code. Sans SMTP_HOST, rien n'est envoyé (le lien « Mot de passe oublié » est alors masqué).

FournisseurOffre gratuite+−
Brevo (recommandé)300 emails/jour, sans carte bancaireSociété française, données dans l'UE (RGPD), SMTP simpleInterface un peu chargée
Resend3 000/mois (100/jour)Très simple, bonne délivrabilitéSociété américaine
Mailjet200/jourEuropéenQuota plus faible
Gmail + mot de passe d'application500/jourRien à créerLie l'app à un compte personnel, délivrabilité moyenne

Pour un foyer (quelques emails par an), Brevo est largement suffisant.

8.1 Brevo, pas à pas​

  1. Créer un compte gratuit sur brevo.com.
  2. Senders, Domains & Dedicated IPs → Domains → Add a domain : fs0ciety.org.
  3. Brevo affiche des enregistrements DNS (code Brevo, DKIM, DMARC). Les ajouter dans Cloudflare → DNS en DNS only (nuage gris : les TXT/CNAME de messagerie ne se proxifient pas), puis « Authenticate » dans Brevo. Si un enregistrement SPF (v=spf1 …) existe déjà sur fs0ciety.org, y ajouter include:spf.brevo.com plutôt que d'en créer un second.
  4. Senders : ajouter l'expéditeur [email protected].
  5. SMTP & API → SMTP : générer une clé SMTP. Renseigner dans Coolify :
VariableValeur
SMTP_HOSTsmtp-relay.brevo.com
SMTP_PORT587
SMTP_USERl'identifiant SMTP affiché par Brevo (…@smtp-brevo.com)
SMTP_PASSWORDla clé SMTP
EMAIL_FROMTandem <[email protected]>
  1. Redéployer, puis tester « Mot de passe oublié » avec votre adresse.

Resend : SMTP_HOST=smtp.resend.com, SMTP_USER=resend, SMTP_PASSWORD=<clé API>, après vérification du domaine de la même façon.

9. Google Calendar et connexion Google​

Un seul client OAuth pour les deux usages. Dans la Google Cloud Console, idéalement avec le compte Google du foyer :

  1. Créer un projet (ex. « Tandem ») ; APIs & Services → Library : activer Google Calendar API.
  2. OAuth consent screen (Google Auth Platform) : type External, nom « Tandem », email d'assistance, domaine autorisé fs0ciety.org. Data access : ajouter les scopes openid, email, profile, …/auth/calendar.calendarlist.readonly et …/auth/calendar.events — rien de plus (pas …/auth/calendar). Branding : Application privacy policy link = https://agenda.fs0ciety.org/privacy (renseigner PRIVACY_CONTACT_EMAIL dans Coolify pour y afficher une adresse de contact).
  3. Audience → Publish app : passer en « In production ». Indispensable : en Testing, Google invalide les autorisations au bout de 7 jours et la synchro s'arrêterait chaque semaine (risque R1). Sans vérification Google, l'écran de consentement affiche « Google n'a pas validé cette application » : cliquer Paramètres avancés → Accéder à Tandem (normal pour une app personnelle, limite de 100 utilisateurs). Pour ouvrir l'app à d'autres foyers : faire vérifier l'app, cf. google-oauth-verification.md.
  4. Clients → Create client → Web application :
    • Authorized JavaScript origins : https://agenda.fs0ciety.org
    • Authorized redirect URIs (exactement, sans barre finale) :
      • https://agenda.fs0ciety.org/v1/calendar/google/callback (calendrier)
      • https://agenda.fs0ciety.org/v1/auth/google/callback (connexion avec Google)
  5. Renseigner GOOGLE_CLIENT_ID et GOOGLE_CLIENT_SECRET dans Coolify et redéployer.
  6. Dans l'app : Réglages → Calendrier partagé → Connecter Google Calendar, autoriser, choisir « Commun G & N » → Utiliser ce calendrier. Un seul membre du foyer a besoin de connecter son compte, s'il a le droit « Apporter des modifications aux événements » sur ce calendrier.

Les URI de redirection sont dérivées de WEB_ORIGIN : aucune variable supplémentaire.

Sécurité : un compte Google n'est jamais rattaché automatiquement à un compte existant ayant la même adresse (prise de contrôle possible). Pour lier Google à un compte créé avec un mot de passe : Réglages → Données & confidentialité → Lier Google.

10. Surveillance et maintenance​

Vue complète (sondes internes, page /status, alertes, Uptime Kuma, Prometheus) : monitoring.md.

  • Disponibilité : le workflow GitHub Disponibilité (.github/workflows/uptime.yml) appelle https://agenda.fs0ciety.org/healthz (web → API → base) toutes les 2 heures, depuis l'extérieur du serveur. Après 3 échecs d'affilée, il ouvre un ticket « Site indisponible » (étiquette panne) : GitHub vous prévient par email / sur l'appli mobile (Watch le dépôt ou être propriétaire suffit). Le ticket se ferme tout seul au retour du site.

    • Autre adresse : variable de dépôt UPTIME_URL (Settings → Secrets and variables → Actions → Variables).
    • GitHub peut retarder les tâches planifiées de quelques minutes, et les suspend après 60 jours sans activité sur le dépôt (un clic sur Enable workflow les relance).
    • Alternative avec alerte SMS / appli dédiée : UptimeRobot ou Better Stack (gratuits) sur la même URL /healthz. Uptime Kuma est possible, mais sur une autre machine : installé sur le même serveur, il tomberait avec lui.
  • Sauvegardes : service backup unhealthy s'il n'y a pas eu de sauvegarde réussie depuis 26 h → activer les notifications Coolify (Settings → Notifications, email ou Telegram).

  • Mises à jour des dépendances : Dependabot (.github/dependabot.yml) ouvre chaque lundi un PR groupé par écosystème (npm, Gradle, images Docker ; actions GitHub chaque mois) pour les versions mineures et correctifs, et un PR par version majeure. Activer aussi Settings → Code security → Dependabot security updates : une faille connue ouvre un PR immédiatement. La CI valide chaque PR ; fusionner quand elle est verte (les majeures : lire le changelog). Majeures ignorées volontairement (à migrer à la main, de façon coordonnée) :

    • OkHttp 5 : Retrofit 3 et MockWebServer reposent encore sur OkHttp 4 ;
    • PostgreSQL : l'image de backup doit avoir la même majeure que le serveur (un dump pg_dump 17+ contient transaction_timeout, que PostgreSQL 16 ne sait pas restaurer). Changer de majeure = migrer la base (dump / restauration) puis les deux images ensemble ;
    • Node : versions LTS paires uniquement, mises à jour à la main.

    Android : depuis AGP 9, Kotlin est intégré au plugin Android (plus de plugin org.jetbrains.kotlin.android) et les bibliothèques AndroidX récentes exigent compileSdk 37. targetSdk reste une décision séparée (exigence Google Play).

  • Logs : Coolify → ressource → Logs (JSON structuré pino côté API ; aucun token ni cookie n'y figure).

  • Erreurs (Sentry, facultatif) : sans configuration, les erreurs du serveur, du site et de l'app Android sont déjà écrites dans les logs de api (message client error / server error). Pour être alerté par email avec le détail (pile d'appels, navigateur, version de l'app) :

    1. Créer un compte gratuit sur https://sentry.io (offre Developer, suffisante pour deux personnes ; ou GlitchTip, compatible, auto-hébergeable).
    2. Create Project → plateforme Node.js → nom agenda. Copier le DSN affiché (https://…@o….ingest.sentry.io/…).
    3. Coolify → variable SENTRY_DSN = ce DSN → redéployer. Un seul DSN suffit : le site et l'app Android envoient leurs erreurs à l'API (/v1/client-errors), qui les transmet.
    4. Vérifier : Sentry → Issues ; les alertes email sont actives par défaut. Aucune donnée personnelle n'est envoyée (ni email, ni contenu des tâches, ni jetons).

11. Checklist de sécurité​

  • Nuage orange actif, SSL Full (strict), Always Use HTTPS.
  • Seul web a un domaine ; api, postgres et redis n'ont ni domaine ni port publié.
  • App OAuth Google en « In production », scopes limités à ceux du §9.
  • Secrets générés aléatoirement, stockés uniquement dans Coolify (+ gestionnaire de mots de passe).
  • REGISTRATION_ENABLED=false après l'inscription du foyer (vaut aussi pour Google Sign-In).
  • Domaine d'envoi authentifié (DKIM/SPF/DMARC) si les emails sont activés.
  • Pare-feu : 80/443 limités aux IP Cloudflare ; SSH par clé uniquement.
  • Dump quotidien actif et copie hors serveur ; restauration testée une fois.
  • Mises à jour de sécurité du serveur et de Coolify planifiées.

12. Dépannage​

SymptômeCause probableSolution
526 Invalid SSL certificateNuage orange avant l'émission du certificat Let's EncryptRepasser en DNS only, attendre le certificat, remettre Proxied
502 / 503 depuis Traefikweb pas encore healthy, ou domaine sans :3000Logs Coolify ; vérifier https://agenda.fs0ciety.org:3000 dans Domains
Connexion OK mais on revient sans cesse sur /loginCookies Secure sur un accès HTTPToujours passer par https:// (Always Use HTTPS)
Tout le monde reçoit « Trop de tentatives »CLIENT_IP_HEADER absent, ou trafic ne passant pas par CloudflareCLIENT_IP_HEADER=cf-connecting-ip, nuage orange
/v1/* renvoie 500 « Internal Server Error » en texte brutAPI injoignable depuis webVérifier que le service s'appelle bien api et qu'il est healthy
L'API redémarre en boucleVariable manquante/invalide (message Invalid environment) ou migration en échecLogs du service api
redirect_uri_mismatch chez GoogleURI absente ou différente dans le client OAuthCopier exactement https://agenda.fs0ciety.org/v1/calendar/google/callback (§9)
« La connexion avec Google a expiré ou a été ouverte dans un autre navigateur »Cookie du flux absent : plus de 10 min sur l'écran Google, navigateur différent (ex. lien ouvert depuis une autre app), cookies bloquésRelancer depuis le même navigateur, sans navigation privée
« Google a refusé la connexion » + log TOKEN_ENCRYPTION_KEY must be 32 …Clé de chiffrement invalide (depuis ce correctif, l'API refuse de démarrer avec ce message)openssl rand -base64 32 → coller le résultat (44 caractères, finit par =) dans TOKEN_ENCRYPTION_KEY, redéployer, reconnecter Google
« Google n'a pas donné accès à votre calendrier »Cases décochées sur l'écran de consentement GoogleRelancer et cocher les deux cases
« Google a refusé la connexion »Échange du code refusé : GOOGLE_CLIENT_SECRET erroné, client OAuth différent, API Calendar non activéeLogs api : ligne Calendar connection failed: Google API 401 unauthorized (invalid_client) ⇒ secret ; vérifier §9
« Google Calendar n'est pas configuré » dans RéglagesGOOGLE_CLIENT_ID/SECRET videsLes renseigner dans Coolify, redéployer
Synchro arrêtée au bout d'une semaineApp OAuth restée en TestingLa publier « In production », puis Reconnecter dans Réglages
Tâches « en attente » de synchro qui n'avancent pasredis non healthy, ou quota GoogleLogs api (Calendar sync mode: queue, Sweep …) ; le balayage reprend toutes les 10 min
Build web échoue sur next/fontPas d'accès à fonts.googleapis.com pendant le buildAutoriser la sortie réseau du serveur pendant le build

13. Site de documentation​

Le service docs du compose sert ce site (Docusaurus construit, servi par nginx) : guide d'utilisation, technique, confidentialité, nouveautés et référence de l'API. Il est statique et indépendant : il n'a accès ni à l'API ni à la base.

  1. Cloudflare → DNS : enregistrement A agenda-docs vers l'IP du serveur (nuage gris au premier déploiement, puis orange, comme en §3.1). Un nom à un seul niveau sous fs0ciety.org reste couvert par le certificat Cloudflare gratuit (docs.agenda.fs0ciety.org ne le serait pas).
  2. Coolify → service docs → Domains : https://agenda-docs.fs0ciety.org:80.
  3. Autre adresse : variable DOCS_URL (utilisée au build pour les liens et le plan du site), puis redéployer.

Le site est public : il ne contient ni secret ni donnée, seulement la documentation du dépôt. Pour le réserver au foyer, placer le domaine derrière Cloudflare Access (Zero Trust → Access → Applications, gratuit jusqu'à 50 utilisateurs, connexion par code e-mail).

En local : cd apps/docs && npm ci && npm start. Le contenu est le dossier docs/ du dépôt ; la référence de l'API (apps/docs/static/openapi.json) est régénérée par pnpm --filter @agenda/api openapi après une modification de l'API (vérifié en CI).