# 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 - Domaine : https://app.rubis.arthurbarre.fr (DNS A 5413305619) - Route Traefik : `~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis-app.yml.j2` ### 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 : ```bash 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) ```bash 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.