rubis/.claude/deploy-memory.md
ordinarthur 985e817289 docs(deploy-memory): refonte split web/api 2 services
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 03:04:22 +02:00

7.8 KiB

Deploy memory — rubis

Ce repo contient deux déploiements distincts sur la même infra K3s, namespace rubis :

  1. Landing statique (rubis.arthurbarre.fr) — image nginx-alpine.
  2. App SaaS (app.rubis.arthurbarre.fr) — image AdonisJS + React (V1).

Chacun a sa propre image, son propre Dockerfile, son propre workflow CI.


1. Landing (rubis.arthurbarre.fr)

Infra

  • Deployment : rubis · Container : rubis · NodePort : 30109
  • Image : git.arthurbarre.fr/ordinarthur/rubis
  • Domaine : https://rubis.arthurbarre.fr (sous-domaine temporaire — domaine définitif rubis-sur-l-ongle.fr pas encore acheté)
  • Manifests : k3s/{namespace,deployment,service}.yml
  • Route Traefik : ~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis.yml.j2
  • Workflow CI : .gitea/workflows/deploy.yml
  • Source : landing/ (servi par nginx)

Mise à jour

Push git, le CI build + rollout auto (filter sur landing/**, Dockerfile, k3s/{namespace,deployment,service}.yml).


2. App SaaS (app.rubis.arthurbarre.fr)

Architecture en 2 services : nginx en frontal (web) + API Node interne.

Traefik :443 → app.rubis.arthurbarre.fr:30110 → rubis-web (nginx)
                                                    ├─ /        → SPA static (try_files)
                                                    └─ /api/*   → rubis-api (ClusterIP :3333)

rubis-web — frontend (NodePort 30110, exposé)

  • Image : git.arthurbarre.fr/ordinarthur/rubis-web
  • Container : web (nginx-alpine + SPA Vite dist)
  • Sert /assets/* (cache 1y immutable), / (SPA fallback try_files)
  • Reverse-proxy /api/* → rubis-api.rubis.svc.cluster.local:3333
  • Manifest : k3s/app/web.yml
  • Workflow CI : .gitea/workflows/deploy-web.yml
  • Source : apps/web, packages/shared + apps/web/nginx.conf

rubis-api — backend (ClusterIP, interne uniquement)

  • Image : git.arthurbarre.fr/ordinarthur/rubis-api
  • Container : api (Node 22 + AdonisJS V7)
  • Workers BullMQ dans le même process (cf. start/queue.ts)
  • Init container migrate : node ace.js migration:run --force depuis /app/apps/api/build (idempotent)
  • Probes K3s sur /api/v1/health
  • Manifest : k3s/app/api.yml (Deployment + Service ClusterIP + ConfigMap)
  • Workflow CI : .gitea/workflows/deploy-api.yml
  • Source : apps/api, packages/shared

rubis-redis — backend BullMQ + cache (ClusterIP)

  • Image : redis:7.4-alpine
  • PVC 1Gi local-path, AOF on, maxmemory 256mb allkeys-lru
  • Manifest : k3s/app/redis.yml
  • Re-déployé par le workflow API (path filter inclut redis.yml)

Infra commune

Dépendances externes (déjà déployées)

  • Postgres : 10.10.10.3:5432, base rubis_prod, user rubis
  • MinIO : minio.minio.svc.cluster.local:9000, bucket rubis-prod-invoices, creds = root MinIO
  • Redis : interne au namespace rubis, pas de dépendance externe

Secrets K3s (rubis-app-secrets)

Posés une fois manuellement via kubectl create secret generic (jamais dans les manifests). Re-pose si rotation ou ajout :

kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
  -n rubis create secret generic rubis-app-secrets \
  --from-literal=APP_KEY=... \
  --from-literal=PG_PASSWORD=... \
  --from-literal=S3_ACCESS_KEY=... \
  --from-literal=S3_SECRET_KEY=... \
  --from-literal=RESEND_API_KEY=... \
  --from-literal=MISTRAL_API_KEY=... \
  --from-literal=REDIS_PASSWORD="" \
  --dry-run=client -o yaml | kubectl apply -f -

Mise à jour

Push git → un (ou les deux) workflow(s) CI se déclenchent selon les paths modifiés. Build+rollout indépendants.

Path modifié Workflow déclenché
apps/web/**, Dockerfile.web, k3s/app/web.yml deploy-web.yml
apps/api/**, Dockerfile.api, k3s/app/api.yml, k3s/app/redis.yml deploy-api.yml
packages/shared/**, pnpm-lock.yaml, tsconfig.base.json, … les deux

Particularités du Dockerfile.api

  • node ace build plante avec ERR_UNKNOWN_FILE_EXTENSION car @poppinss/ts-exec ne s'enregistre pas à temps avant l'import de bin/console.ts. Solution : pnpm exec tsx ace.js build --ignore-ts-errors (tsx = esbuild, gère .ts nativement).
  • --ignore-ts-errors car tests/bootstrap.ts référence un type généré tardivement (.adonisjs/client/registry/schema.d.ts). Le typecheck strict est exécuté en CI séparée (pnpm typecheck).

Particularités du Dockerfile.web

  • Vite build appelé directement (pnpm exec vite build) au lieu du script tsc -b && vite build qui plante sans cache .tsbuildinfo à cause de @tanstack/router-core.
  • nginx.conf inclut le upstream rubis-api.rubis.svc.cluster.local:3333. Si on renomme le service API, c'est ici qu'il faut mettre à jour (en plus du manifest).

Voie manuelle (debug, hors CI)

TAG=$(git rev-parse --short HEAD)
# Landing (rubis.arthurbarre.fr)
docker build --platform linux/amd64 -t git.arthurbarre.fr/ordinarthur/rubis:$TAG .
docker push git.arthurbarre.fr/ordinarthur/rubis:$TAG
kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
  -n rubis set image deploy/rubis rubis=git.arthurbarre.fr/ordinarthur/rubis:$TAG

# App — web (frontend)
docker build --platform linux/amd64 -f Dockerfile.web \
  -t git.arthurbarre.fr/ordinarthur/rubis-web:$TAG .
docker push git.arthurbarre.fr/ordinarthur/rubis-web:$TAG
kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
  -n rubis set image deploy/rubis-web web=git.arthurbarre.fr/ordinarthur/rubis-web:$TAG

# App — api (backend)
docker build --platform linux/amd64 -f Dockerfile.api \
  -t git.arthurbarre.fr/ordinarthur/rubis-api:$TAG .
docker push git.arthurbarre.fr/ordinarthur/rubis-api:$TAG
kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
  -n rubis set image deploy/rubis-api api=git.arthurbarre.fr/ordinarthur/rubis-api:$TAG \
  && kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
       -n rubis patch deploy/rubis-api --type=json \
       -p="[{\"op\":\"replace\",\"path\":\"/spec/template/spec/initContainers/0/image\",\"value\":\"git.arthurbarre.fr/ordinarthur/rubis-api:$TAG\"}]"

⚠️ Cross-compile ARM Mac → linux/amd64 plante sur @swc/core au build de l'app. Préférer pousser et laisser le CI Gitea (linux/amd64 natif).

Si changement route/domaine : cd ~/dev/perso/proxmox/ansible && ansible-playbook playbooks/gateway.yml. Penser à sudo systemctl restart traefik sur la gateway si un nouveau domaine ne récupère pas son cert ACME tout seul (le hot-reload de la dynamic config ne déclenche pas toujours le challenge LE).


Quand le domaine définitif sera acheté (rubis-sur-l-ongle.fr)

  1. Records DNS chez OVH : A @, A app, MX/SPF/DKIM pour Resend
  2. Modifier les rules dans rubis.yml.j2 et rubis-app.yml.j2 (Host)
  3. Replay gateway.yml
  4. Maj MAIL_FROM_ADDRESS=relances@rubis-sur-l-ongle.fr dans le secret
  5. (Optionnel) supprimer les A records rubis.arthurbarre.fr et app.rubis.arthurbarre.fr chez OVH

Déjà fait — NE PAS refaire

  • Dockerfile (landing) + Dockerfile.web + Dockerfile.api (app split)
  • nginx.conf (apps/web/nginx.conf) avec upstream rubis-api
  • Manifests k3s/ (landing) + k3s/app/{api,web,redis}.yml
  • Routes Traefik rubis.yml.j2 + rubis-app.yml.j2
  • DNS OVH : A records rubis (id 5413044152) + app.rubis (id 5413305619)
  • Repo Gitea + secrets CI (KUBECONFIG, REGISTRY_PASSWORD)
  • Namespace + secret registry K3s (gitea-registry)
  • Postgres : base rubis_prod + user rubis (10.10.10.3)
  • MinIO : bucket rubis-prod-invoices
  • Secret K3s rubis-app-secrets (APP_KEY, DB pwd, MinIO, Resend, Mistral)
  • ConfigMap rubis-api-config (env non-sensibles)

Les prochains /deploy font uniquement build + rollout via push git.