Environnements
Dev local, staging, prod — Docker Compose, variables d'environnement, domaines
Un seul
docker-compose.ymlà la racine pour toute l'infra locale (Postgres, Redis, Signoz, Reacher, RustFS). Profiles Docker pour lancer seulement ce dont on a besoin.
Les 3 environnements
| Env | Où | DB | Redis | Monitoring | Usage |
|---|---|---|---|---|---|
| dev | laptop, Docker Desktop | Postgres local | Redis local | Signoz local (optionnel) | développement quotidien |
| staging | Dokploy sur VPS staging | Postgres dédié | Redis dédié | Signoz staging | QA, démo client |
| prod | Dokploy sur VPS prod | Postgres HA | Redis persistent | Signoz prod | utilisateurs réels |
Dev local
Pré-requis
- Bun 1.3+ (bun.sh)
- Docker Desktop (ou Colima)
- Node 20+ (pour certains outils comme drizzle-kit)
- ~8 GB RAM libre si on lance Signoz (ClickHouse mange)
Démarrage
# 1. Installer les deps
bun install
# 2. Copier les fichiers .env d'exemple
cp apps/server/.env.example apps/server/.env
cp apps/pwa/.env.example apps/pwa/.env # à créer quand apps/pwa existera
cp apps/dashboard/.env.example apps/dashboard/.env
# 3. Lancer l'infra minimale (Postgres + Redis)
bun db:start
# 4. Push le schema Drizzle
bun db:push
# 5. Lancer les apps en dev
bun dev # web + server + docsProfiles Docker
Le docker-compose.yml racine utilise des profiles pour ne pas tout lancer systématiquement :
| Profile | Services | Commande | Quand l'utiliser |
|---|---|---|---|
db | postgres, redis | bun db:start | Dev quotidien — requis |
validators | reacher | bun infra:start | Quand on teste un flow qui envoie des emails |
monitoring | signoz-* (ClickHouse + Collector + UI + Alertmanager) | bun infra:monitoring | Quand on teste l'observabilité — gourmand en RAM |
storage | rustfs | bun infra:storage | Quand on touche aux uploads |
Scripts raccourcis racine :
bun db:start # postgres + redis (profile db)
bun infra:start # db + validators (stack dev courant)
bun infra:monitoring # ajoute Signoz
bun infra:storage # ajoute RustFS
bun infra:full # tout d'un coup
bun infra:stop # stop sans supprimer les volumes
bun infra:down # stop + supprime containers (garde volumes)
bun infra:clean # down + supprime volumes (⚠️ perte données)Endpoints locaux
| Service | URL | Credentials |
|---|---|---|
| Postgres | localhost:5432 | bell / bell_dev_password / db bell |
| Postgres (test DB) | localhost:5432 | bell / bell_dev_password / db bell_test |
| Redis | localhost:6379 | pas de password en dev |
| Server (Elysia) | http://localhost:3000 | — |
| OpenAPI / Swagger | http://localhost:3000/openapi | — |
| Web / Dashboard | http://localhost:3001 (split pwa/dashboard à venir) | — |
| Fumadocs | http://localhost:4000 | — |
| Reacher | http://localhost:8080 | — |
| Signoz UI | http://localhost:3301 | — |
| RustFS Console | http://localhost:9001 | bell / bell_dev_password |
| RustFS S3 | http://localhost:9000 | idem |
| Drizzle Studio | bun db:studio ouvre un browser | — |
Fichier .env du server
Exemple pour apps/server/.env :
# ─── Core ─────────────────────────────────────────────────────
PORT=3000
NODE_ENV=development
# ─── Database ─────────────────────────────────────────────────
DATABASE_URL=postgresql://bell:bell_dev_password@localhost:5432/bell
DATABASE_TEST_URL=postgresql://bell:bell_dev_password@localhost:5432/bell_test
# ─── Redis (BullMQ + SSE pub/sub) ─────────────────────────────
REDIS_URL=redis://localhost:6379
# ─── Auth (Better Auth) ───────────────────────────────────────
BETTER_AUTH_SECRET=<generate via `openssl rand -base64 32`>
BETTER_AUTH_URL=http://localhost:3000
CORS_ORIGIN=http://localhost:3001,http://localhost:3002
# ─── AI providers (cf. ADR-10) ────────────────────────────────
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
AI_MODEL_CONCIERGE=claude-haiku-4-5
AI_MODEL_CONCIERGE_STRONG=claude-sonnet-4-6
AI_MODEL_AUXILIARY=gpt-4o-mini
AI_MODEL_EMBEDDINGS=text-embedding-3-small
AI_PROMPT_CACHING_ENABLED=true
AI_RAG_GLOBAL_ENABLED=false
# ─── Stripe ────────────────────────────────────────────────────
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# ─── SMTP (Gmail Workspace) ───────────────────────────────────
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=noreply@hoaiy.com
SMTP_PASS=<app password>
SMTP_FROM=Bell <noreply@hoaiy.com>
# ─── Reacher (email validation) ───────────────────────────────
REACHER_ENABLED=true
REACHER_URL=http://localhost:8080
# ─── OpenTelemetry → Signoz ───────────────────────────────────
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
DEPLOYMENT_ENV=development
# ─── Feature flags ────────────────────────────────────────────
OPENAPI_ENABLED=trueValidation : toutes les env vars sont validées au boot par @bell/env (Zod). Si une variable est manquante ou mal formée, le serveur refuse de démarrer avec un message clair.
Fichier .env des fronts (à venir)
NEXT_PUBLIC_SERVER_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3001Les fronts utilisent le proxy Next.js rewrites (cf. ADR-02) pour éviter le cross-origin en dev.
Staging
VPS : un VPS chez Hostinger/Proxmox, même stack que prod mais data isolée. Branchement Dokploy.
- Domaines :
staging-app.hoaiy.com,staging-staff.hoaiy.com,staging-api.hoaiy.com,staging-docs.hoaiy.com - Cookies :
domain: .hoaiy.compartagé prod/staging → attention, on isole viasameSite+ sub-domaines dédiés - Postgres / Redis dédiés (containers séparés de la prod)
- Signoz : instance séparée ou partagée avec tag
deployment.environment=staging
Promotion dev → staging
Automatique via GitHub Actions : push sur main → build Docker → push vers registry → Dokploy webhook → deploy.
Seed staging
Seed identique à la prod au reset (scénarios de démo). Mews connecté à la sandbox.
Prod
VPS : Proxmox Hostinger (Larnaca, Chypre — choix RGPD). Dokploy orchestre les containers.
Domaines
| App | Prod |
|---|---|
| PWA Guest | bell-app.hoaiy.com |
| Dashboard | bell-staff.hoaiy.com |
| API | bell-api.hoaiy.com |
| Docs | bell-docs.hoaiy.com (basic auth) |
| Signoz UI | bell-obs.hoaiy.com (basic auth + IP allowlist) |
DNS : wildcard *.hoaiy.com → Traefik reverse proxy.
Cert TLS
Let's Encrypt via Traefik, auto-renouvelé.
Secrets prod
Aucun secret en plain text dans le repo. Les .env prod sont injectés au deploy via Dokploy (dashboard UI) qui les écrit dans un volume chiffré. Les valeurs sensibles (STRIPE_SECRET_KEY, BETTER_AUTH_SECRET, DB passwords) sont uniques prod ≠ staging ≠ dev.
docker-compose.prod.yml
Le fichier prod (docker-compose.prod.yml) est un overlay du docker-compose.yml dev :
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -dIl ajoute :
- Les services apps (server, worker, pwa, dashboard, fumadocs) avec builds
- Labels Traefik pour le routing + TLS
- Overrides infra (passwords, pas d'exposition de ports, Redis require pass)
Backups
- Postgres : dump quotidien via cron sur l'hôte → upload vers Cloudflare R2 (S3 compatible), retention 30 jours.
- Redis : snapshot AOF persistent sur volume Docker (pas de perte BullMQ si restart).
- Uploads RustFS : réplication vers R2.
- Signoz ClickHouse : retention 30 jours en interne, pas de backup long terme (observabilité = éphémère).
Monitoring des backups
Un job cron envoie une alerte Signoz si un dump Postgres n'a pas tourné dans les 26 dernières heures.
Rollback
- Code : Dokploy rollback vers l'image précédente en un clic.
- Schema DB : via migrations Drizzle réversibles (
drizzle-kit migrate:rollback). - Data : restore depuis le dump R2 si besoin (temps de restore estimé : ~15 min pour 10 GB).
Variables d'environnement — référence
Voir packages/env/src/server.ts et packages/env/src/web.ts pour le schema Zod complet. Tout changement de schema = update simultané des .env.example.
Catégories :
| Catégorie | Variables | Où |
|---|---|---|
| Core | PORT, NODE_ENV | server, worker |
| Database | DATABASE_URL, DATABASE_TEST_URL | server, worker |
| Redis | REDIS_URL | server, worker |
| Auth | BETTER_AUTH_SECRET, BETTER_AUTH_URL, CORS_ORIGIN | server |
| AI | ANTHROPIC_API_KEY, OPENAI_API_KEY, AI_MODEL_*, AI_PROMPT_CACHING_ENABLED | server, worker |
| Stripe | STRIPE_* | server, worker |
SMTP_* | server, worker | |
| Reacher | REACHER_ENABLED, REACHER_URL | server, worker |
| OpenTelemetry | OTEL_EXPORTER_OTLP_ENDPOINT, DEPLOYMENT_ENV | toutes les apps |
| Feature flags | OPENAPI_ENABLED, autres | server |
| Fronts | NEXT_PUBLIC_SERVER_URL, NEXT_PUBLIC_APP_URL | pwa, dashboard |
Debug par environnement
Dev qui ne démarre pas
Checklist :
bun db:starta-t-il bien démarré ?docker psdoit montrerbell-postgreshealthy..envprésent dansapps/server/? Au besoincp apps/server/.env.example apps/server/.env.- Schema pushé ?
bun db:pushdoit finir sans erreur. - Port 3000 libre ?
lsof -i :3000→ si pris, tuer le process.
Prod qui lag
- Signoz → dashboard API par module, voir si un module spike la p95.
- Bull Board (
bell-obs.hoaiy.com/queues) → y a-t-il des queues qui s'empilent ? - Postgres
pg_stat_activity→ y a-t-il une requête qui tourne longtemps ? - Redis
CLIENT LIST→ connexions normales (SSE + BullMQ + app) ?
Cf. monitoring pour les runbooks détaillés.
Lien avec les autres pages
- Auth — config Better Auth, domaines cookies, magic link
- Monitoring — dashboards Signoz, alertes
- Concurrency — BullMQ queues, workers, DLQ
- Onboarding hôtel — créer org, inviter staff, connecter Mews, seed
- ADR-04 jobs hybrides
- ADR-08 monitoring Signoz