Déploiement sur Data Clerk
Cette application fonctionne sur Data Clerk, un petit cluster Kubernetes géré par un opérateur (une personne, contact dans PROJECT.md). Le propriétaire de ce dépôt n'est généralement pas une personne d'infrastructure. Il ne doit jamais avoir à toucher aux serveurs, à Kubernetes ou à une console cloud : il pousse sur main, et le cluster fait le reste. Votre travail est de maintenir cet état de fait : travaillez dans le dépôt, suivez le contrat ci-dessous, et lorsque quelque chose nécessite réellement l'intervention du cluster, rédigez une demande claire à l'opérateur au lieu d'improviser.
Expliquez les choses à l'utilisateur en termes simples ("votre site sera mis à jour dans environ cinq minutes"), et non en termes Kubernetes.
Commencez ici
- Lisez
deploy-data-clerk/PROJECT.md. Il contient les valeurs de ce projet : URL publique, dossier de déploiement, nom de l'image, port, commande de migration, limites, qui est l'opérateur. Tout ce qui ci-dessous dit "depuis PROJECT.md" fait référence à ce fichier. Si le nom du dossier dans PROJECT.md diffère dedeploy-data-clerk/(les anciens projets utilisentdeploy/), utilisez ce que dit PROJECT.md. - Pas de
PROJECT.md, ou il contient encore des valeurs__PLACEHOLDER__? Le projet n'est pas encore intégré : suivezreferences/new-project.md. - Écrire ou modifier un Dockerfile, choisir comment structurer l'application, ou la stack n'est pas Node ? Lisez
references/stacks.md(n'importe quel langage, full-stack vs frontend séparé + API, FastAPI, Go). - Avant la première tâche liée aux secrets sur une machine, assurez-vous que les outils fonctionnent :
sops --versionet un déchiffrement de test (sops decrypt <dossier de déploiement>/secrets.env > /dev/null). Si l'un des deux échoue, suivezreferences/setup-tools.md.
Comment une modification atteint le site en ligne
| Ce qui a changé | Ce qui se passe | En ligne après |
|---|---|---|
Code (push sur main) | GitHub Actions construit une image Docker et la pousse ; le cluster détecte le nouveau tag d'image et le déploie | ~3–7 min (temps de construction + ~1 min) |
secrets.env (push sur main) | Le cluster relit le dossier de déploiement toutes les 5 min, le déchiffre, met à jour l'environnement de l'application et la redémarre | ~5 min |
Un nouveau fichier dans migrations/ | S'exécute automatiquement avant le démarrage de la nouvelle version | avec le déploiement du code |
Tout ce qui est en dehors de main | Rien. Les branches et les PR ne sont pas déployées | — |
Le déploiement est prudent : la nouvelle version démarre à côté de l'ancienne, les migrations s'exécutent en premier, et le trafic est basculé uniquement lorsque la nouvelle version répond à sa vérification de santé. Si la construction, une migration ou la vérification de santé échoue, l'ancienne version continue de servir et le site reste en ligne. Un déploiement échoué signifie "la modification n'est pas en ligne", pas "le site est en panne".
Pour vérifier un déploiement : gh run list --workflow "Deploy to cluster" --limit 3 (ou l'onglet Actions sur GitHub), puis ouvrez l'URL publique. Pour redéployer sans modification de code : gh workflow run "Deploy to cluster", ce qui construit un nouveau tag, donc l'application redémarre avec un environnement frais.
Le contrat (ce qui maintient les déploiements fonctionnels)
Chaque règle existe parce que l'enfreindre échoue silencieusement ou perd des données. Respectez-les et dites à l'utilisateur si une demande en enfreindrait une.
- Écoutez sur
$PORT(le port dans PROJECT.md, défini par le cluster), sur0.0.0.0, paslocalhost. - L'image se construit à partir de
<dossier de déploiement>/Dockerfilepourlinux/amd64et s'exécute en tant qu'utilisateur non-root. Pas de root, pas de ports en dessous de 1024, pas de capacités Linux supplémentaires. - Les tags d'image sont
YYYYMMDDHHMMSS-<shortsha>. Le cluster déploie le nombre le plus élevé et ignore silencieusement tout tag qui n'est pas dans ce format. Ne modifiez jamais l'étape de tag dans.github/workflows/deploy-cluster.yml. - Le disque du conteneur est effacé à chaque déploiement et redémarrage. Ne conservez jamais les téléchargements d'utilisateurs, les fichiers SQLite ou quoi que ce soit de précieux sur le disque local. Mettez les données dans la base de données ; pour les fichiers (photos, vidéos), demandez à l'opérateur un stockage d'objets.
- Vérification de santé : le cluster sonde le chemin de santé dans PROJECT.md (
/healthzrecommandé). Faites-le retourner 200 rapidement, sans appel à la base de données ni rendu de page, sinon une base de données lente ferait croire au cluster que l'application est morte. DATABASE_URLprovient du cluster. Ne le mettez jamais danssecrets.env(cela écraserait le vrai) et ne le définissez jamais au moment de la construction de l'image.- Exécution vs construction.
secrets.envatteint l'application uniquement à l'exécution. L'image est construite dans GitHub Actions sans lui. Tout ce qui est intégré dans le frontend au moment de la construction (VITE_*,NEXT_PUBLIC_*,REACT_APP_*) ne peut pas provenir desecrets.env: lisez ces valeurs sur le serveur à l'exécution et transmettez-les à la page, ou (si ce n'est pas secret) engagez-les dans le code. - Secrets que l'application génère elle-même lorsqu'ils sont manquants (secret d'authentification/session, sels de chiffrement, clés de push) : définissez-les dans
secrets.env. Sinon, chaque redémarrage en crée de nouveaux, les utilisateurs sont déconnectés, les abonnements push sont interrompus, etc. - Les migrations doivent être sûres pour l'ancienne version. Pendant environ une minute, les deux versions s'exécutent avec le nouveau schéma. Ajoutez des colonnes et des tables librement. Renommez ou supprimez en deux déploiements : d'abord cessez de l'utiliser, puis supprimez-le. Détails dans
references/database.md.
Variables d'environnement et secrets
Toute la configuration d'exécution se trouve dans <dossier de déploiement>/secrets.env, commité chiffré avec sops. Seules les valeurs sont chiffrées ; les noms de clés restent lisibles. Modifiez-le avec sops lui-même. Ne le déchiffrez jamais dans un fichier que vous pourriez commiter, et n'imprimez jamais les valeurs secrètes dans le chat ou les logs.
sops set deploy-data-clerk/secrets.env '["STRIPE_SECRET_KEY"]' '"sk_live_..."'
sops unset deploy-data-clerk/secrets.env '["OLD_KEY"]'
sops decrypt deploy-data-clerk/secrets.env | cut -d= -f1
Ensuite, commitez et poussez sur main ; c'est en ligne dans ~5 min. Lorsque l'utilisateur doit fournir la valeur (une clé API depuis un tableau de bord), faites-lui exécuter la ligne sops set lui-même, ou collez la valeur uniquement dans son propre terminal. Le guide complet (clés, où elles se trouvent, rotation, pièges) est dans references/secrets.md.
Base de données
Postgres 18, gérée par l'opérateur. L'application reçoit sa propre base de données et son propre utilisateur via DATABASE_URL. Les modifications de schéma sont des fichiers de migration qui s'appliquent eux-mêmes lors du déploiement (references/database.md).
Sauvegardes : une sauvegarde complète chaque nuit à 03h00 UTC plus un journal continu de chaque modification, stocké hors serveur dans Google Cloud Storage. L'opérateur peut restaurer la base de données à n'importe quel moment des 7 derniers jours. Donc si des données sont supprimées ou si une migration tourne mal, ne paniquez pas et n'essayez pas de le réparer à la main : notez l'heure à laquelle c'est arrivé et demandez à l'opérateur une restauration (modèle dans references/troubleshooting.md). Une restauration rembobine toute la base de données, donc les modifications effectuées après ce moment sont perdues.
Faits réseau dont votre code peut avoir besoin
- Chemin du trafic : visiteur → Cloudflare → Traefik (entrée du cluster) → application. Les certificats HTTPS sont automatiques. L'application elle-même parle HTTP simple sur
$PORT;X-Forwarded-Proto: httpsindique que l'original était HTTPS. Faites confiance aux en-têtes du proxy et ne redirigez pas vers HTTPS dans l'application (cela créerait une boucle). - Adresse IP réelle du visiteur : lisez l'en-tête
CF-Connecting-IP.X-Forwarded-Forcontient une adresse Cloudflare, donc la limitation de débit basée sur celui-ci mettrait tous les visiteurs dans le même panier. - Traefik ajoute ces en-têtes : HSTS (2 ans, preload),
X-Content-Type-Options: nosniff,X-Frame-Options: SAMEORIGIN(le site ne peut pas être intégré dans une iframe sur d'autres domaines). Les en-têtes supplémentaires (CSP, COOP/COEP pourSharedArrayBuffer, autorisation des iframes) sont définis par l'opérateur sur demande. - L'entrée ne compresse pas les réponses. Cloudflare compresse les types de texte courants ; pour un contrôle total, compressez dans l'application ou livrez des actifs précompressés.
- Les connexions longues (SSE, websockets) sont acceptables, mais Cloudflare interrompt une connexion inactive pendant ~100 s. Envoyez un heartbeat toutes les ~30 s et reconnectez-vous côté client.
- Sortant : l'application peut appeler n'importe quel point de terminaison internet public sur n'importe quel port. Cela inclut les API LLM, Stripe, email, S3, et les bases de données gérées ou Redis (Neon, Supabase, Atlas, Upstash) sur leurs points de terminaison publics. Elle ne peut pas atteindre les réseaux privés (peering VPC, PrivateLink, IP privées) ni les autres projets sur le cluster. Le trafic entrant ne provient que via l'URL publique.
- Services tiers : mettez leurs clés et URL dans
secrets.env. Si un service a besoin d'une liste d'autorisation IP, le cluster a une IP sortante statique : demandez-la à l'opérateur (elle n'est pas publiée) et dites à l'utilisateur qu'elle est partagée avec d'autres projets sur le cluster. Le serveur est dans le centre des États-Unis, donc privilégiez les régions US-central/us-est pour les bases de données externes (une base de données en UE ajoute ~100 ms par requête). Une base de données externe est la propriété de l'utilisateur : les sauvegardes de Data Clerk ne la couvrent pas. Pour les appels LLM lents, diffusez la réponse ou exécutez-les en tant que tâches d'arrière-plan ; une requête de navigateur attendant plus de ~100 s est coupée. - Les ressources sont plafonnées (mémoire/CPU dans PROJECT.md). Dépasser la limite de mémoire tue et redémarre l'application. Si cela se produit, demandez à l'opérateur plus de place plutôt que de lutter contre cela dans le code.
- Une seule copie de l'application s'exécute (un réplica). Ne comptez pas sur l'état en mémoire survivant à un déploiement et conservez les sessions dans la base de données ou dans des cookies signés.
Ce que seul l'opérateur peut faire
L'utilisateur n'a pas accès au cluster, et c'est fait exprès. Écrivez à l'opérateur une courte demande (voir references/troubleshooting.md pour le modèle) pour :
nouveau domaine ou sous-domaine · plus de mémoire/CPU · stockage de fichiers/objets · une restauration de base de données · logs de l'application ou raisons de crash · en-têtes de sécurité modifiés · modification de la commande de migration ou de démarrage, du port ou du chemin de santé · tâches planifiées (cron) · services supplémentaires (Redis, files d'attente) · une deuxième application dans le même projet · intégration d'un nouveau dépôt.
Plus de détails
references/stacks.md: le contrat universel pour n'importe quel langage ; full-stack vs SPA + API vs statique ; Node, Python FastAPI, Go et autres stacks.references/new-project.md: intégration d'un dépôt (fichiers à ajouter, quoi demander à l'opérateur, Dockerfile par stack).references/secrets.md: sops en profondeur, le fichier de clé, rotation, variables de construction.references/database.md: migrations, chaîne de connexion, sauvegardes, correctifs de données.references/setup-tools.md: installation sécurisée de sops/git/gh sur macOS, Linux et Windows. Exécutezscripts/install-tools.shouscripts/install-tools.ps1.references/troubleshooting.md: "ma modification n'est pas en ligne", "le site est en panne", erreurs courantes, le modèle de demande à l'opérateur.assets/: modèles de Dockerfile (Node, Python, Go) et modèles pour le workflow de déploiement,kustomization.yaml,.sops.yaml,.gitattributesetPROJECT.md.