Environnements
4 environnements, Local, Preprod, Prod, Training. Tous self-host via Dokploy
4 environnements bien séparés pour développer en confiance, valider en preprod, livrer en prod, et entraîner l'IA sans polluer les données réelles.
Vue d'ensemble
| Env | Hébergement | Données | URLs | Branche |
|---|---|---|---|---|
| Local | Docker Compose (docker-compose.dev.yml) | Seed faker | http://localhost:3001 (web), :3000 (api) | n'importe |
| Preprod | Dokploy (Hostinger VPS), docker-compose.preprod.yml | Snapshot prod via dump shareable | app.preprod.reciprok.co (web), api.preprod.reciprok.co (api) | develop |
| Prod | Dokploy (Hostinger VPS), docker-compose.prod.yml | Réelles | app.reciprok.co (web), api.reciprok.co (api), monitoring.reciprok.co (Signoz) | main |
| Training | Dokploy (Hostinger) | Snapshot prod nightly | training.reciprok.co | main |
Aucun environnement n'utilise de service tiers managé pour la base ou l'app (pas de Neon, Supabase, Vercel, etc.). Toute l'infra tourne sur Hostinger VPS + Dokploy.
Local
Stack Docker Compose
services:
postgres:
image: pgvector/pgvector:pg18
environment:
POSTGRES_USER: reciprok
POSTGRES_PASSWORD: reciprok
POSTGRES_DB: reciprok
ports: ["5432:5432"]
volumes:
- postgres-data:/var/lib/postgresql/data
rustfs:
image: rustfs/rustfs:latest
ports: ["9000:9000", "9001:9001"]
volumes:
- rustfs-data:/data
- rustfs-logs:/logs
mailcatcher:
image: dockage/mailcatcher
ports: ["1025:1025", "1080:1080"]
volumes:
postgres-data:
rustfs-data:
rustfs-logs:- Postgres : pgvector en local, données seed via
bun db:seed - RustFS : alternative S3-compatible à MinIO, écrite en Rust, drop-in remplacement, plus performant et plus léger. Évite de polluer le bucket R2 prod. Console à
localhost:9001(creds par défautrustfsadmin/rustfsadmin) - Mailcatcher : capture les emails sortants, les rend visibles à
localhost:1080
Variables d'env
.env.local (ignoré git) :
DATABASE_URL=postgres://reciprok:reciprok@localhost:5432/reciprok
S3_ENDPOINT=http://localhost:9000
S3_BUCKET=reciprok-dev
S3_ACCESS_KEY_ID=rustfsadmin
S3_SECRET_ACCESS_KEY=rustfsadmin
S3_FORCE_PATH_STYLE=true
SMTP_HOST=localhost
SMTP_PORT=1025
ANTHROPIC_API_KEY=sk-ant-... # vraie clé en dev (pas d'alternative locale viable)
OPENAI_API_KEY=sk-...
WEB_ORIGIN=http://localhost:3000Les clés API IA sont les vraies (Anthropic, OpenAI). Les coûts en dev sont minimes (compteur en place dès le jour 1).
Commandes
bun docker:up # démarre Postgres + RustFS + Mailcatcher
bun db:migrate # applique les migrations Drizzle
bun db:seed # peuple avec ~50 membres et 5 demandes fictives
bun dev # lance server (Elysia) + web (Next.js) en parallèle (turbo)Preprod
Objectif
Valider les déploiements avant prod, tester sur des données réalistes mais anonymisées.
Spécificités
- Image Docker :
apps/{server,web}/Dockerfilereconstruites à chaque deploy par Dokploy depuis la branchedevelop. Pas de registry intermédiaire. - Base de données : Postgres self-host (service
postgresdu compose preprod), pgvector inclus. Schéma identique à prod. - Données : seed manuel via
db:importd'un dump shareable produit par un admin (SHAREABLE=1 bun run db:export), pas de pipeline d'anonymisation auto pour l'instant. - Storage : RustFS self-host (service
rustfs), bucketreciprok-preprod. - Email : Resend Pro avec
RESEND_DEV_TOqui redirige tous les envois vers une adresse de test (eviter de spam les vrais destinataires). - WhatsApp : numéro de test Meta Cloud (pas de message réel sortant).
- Domaines :
app.preprod.reciprok.co(web) +api.preprod.reciprok.co(api). Routage via Dokploy UI → labels Traefik injectés automatiquement.
Pipeline de déploiement
Validation manuelle obligatoire avant tout merge sur main.
CI smart, Nx affected
Le job principal affected du workflow ci.yml n'exécute que les check-types, tests et builds des projets impactés par le diff entre la branche cible et HEAD. Concrètement :
- Une PR qui ne touche que
apps/web→ seuls le check-types, les tests et le build deweb(et de ses dépendances internes) tournent - Une PR qui ne touche que
apps/fumadocs→ seuls les jobs defumadocs - Une PR qui touche
packages/db→ tout ce qui dépend de la DB est rejoué (server, web, workers) - Un push sur
main→ recalcul à partir du dernier commit "successful"
Ça repose sur :
nrwl/nx-set-shas@v4qui calculeNX_BASEetNX_HEADautomatiquement selon le contexte (PR ou push)nx affected -t <task>qui filtre les projets impactés via le graphe de dépendances Nx- Un cache Nx local persisté entre runs (clé
bun.lock+ SHA)
Avantage : un changement ciblé sur le frontend ne bloque pas une heure de pipeline pour rejouer le backend qui n'a pas bougé.
Prod
Objectif
L'environnement réel utilisé par l'équipe Reciprok et les organisateurs.
Spécificités
- Image Docker : reconstruite par Dokploy depuis la branche
main(déploiement manuel uniquement, pas d'autodeploy On Push). - Base de données : Postgres self-host (service
postgresdu compose prod) + pgvector. Backups quotidiens via Dokploy "Backups" → bucket S3 externe. - Storage : RustFS self-host (service
rustfs), bucketreciprok-prod. Migré depuis Vercel Blob en 2026 viadb:migrate-images. - Email : Resend production (domaine
reciprok.covérifié). - WhatsApp : numéro production Meta Cloud, webhook sur
https://api.reciprok.co/webhooks/whatsapp. - Domaines :
app.reciprok.co(web) +api.reciprok.co(api) +monitoring.reciprok.co(Signoz). - Monitoring : stack Signoz embarquée dans le compose (
signoz+clickhouse+zookeeper+otel-collector). Voiroperations/monitoring.
Stratégie de déploiement
- Pas de zero-downtime au démarrage, l'app sert ~5 utilisateurs internes, une coupure de 30 secondes au déploiement est acceptable
- Rolling update prévu plus tard via Traefik + 2 instances Dokploy si le besoin se présente
- Migration de DB : exécutée avant le déploiement de l'image. Si la migration échoue, le déploiement est annulé.
Backups
Cron quotidien sur le serveur Postgres :
#!/bin/bash
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
pg_dump -Fc reciprok > /tmp/reciprok_${TIMESTAMP}.dump
rclone copy /tmp/reciprok_${TIMESTAMP}.dump r2:reciprok-backups/daily/
rm /tmp/reciprok_${TIMESTAMP}.dumpPolitique de rétention :
- Daily : 30 jours
- Monthly : 12 mois (1er du mois)
- Yearly : 5 ans (1er janvier)
Test de restauration mensuel sur l'env preprod (procédure documentée).
Training
Objectif
Environnement dédié à l'entraînement et l'évaluation de l'IA sur des données réelles, sans risque de modifier la prod.
Cas d'usage
- Tester de nouveaux prompts système sur des conversations historiques
- Évaluer le pipeline de search sur les vraies demandes passées
- Régénérer les embeddings après changement de modèle
- Lancer des batchs d'analyse historique (KB curation, comparaisons concurrentielles)
Spécificités
- Restore nightly d'un snapshot prod via le cron de backup
- Lecture seule depuis le dashboard, modifications uniquement via scripts ou worker
- Aucun email/WhatsApp sortant : tous les providers sont remplacés par des stubs qui loggent uniquement
- Domaine interne :
training.reciprok.com(accès restreint à l'IP Hostinger + auth) - Compteur de coûts IA séparé : pour ne pas mélanger avec les coûts prod
Restore nightly
#!/bin/bash
LATEST=$(rclone lsf r2:reciprok-backups/daily/ --include "*.dump" | sort | tail -n1)
rclone copy r2:reciprok-backups/daily/${LATEST} /tmp/
pg_restore -c -d reciprok_training /tmp/${LATEST}
rm /tmp/${LATEST}Lancé via cron Dokploy à 4h du matin.
Comparatif rapide
| Aspect | Local | Preprod | Prod | Training |
|---|---|---|---|---|
| Postgres | Docker local | Dokploy | Dokploy | Dokploy |
| Storage | RustFS | R2 preprod | R2 prod | R2 prod (RO) |
| Mailcatcher | Resend test | Resend prod | Stub | |
| Stub | Meta sandbox | Meta prod | Stub | |
| Données | Seed | Anonymisées | Réelles | Snapshot prod |
| Coût IA tracé | Oui (dev) | Oui (preprod) | Oui (prod) | Oui (training, séparé) |
| Auto-deploy | , | branche develop | tag v* sur main | nightly restore |
Secrets
Tous les secrets sont gérés via Dokploy Secrets (variables d'env chiffrées par environnement). Pas de fichier .env commité.
Liste minimale par env :
DATABASE_URLANTHROPIC_API_KEYOPENAI_API_KEYRESEND_API_KEYMETA_WHATSAPP_TOKENR2_ACCESS_KEY/R2_SECRET_KEYBETTER_AUTH_SECRETWEB_ORIGIN
Rotation : trimestrielle pour les clés tierces, annuelle pour BETTER_AUTH_SECRET.
Voir aussi
operations/monitoring, la stack d'observabilité associéeoperations/costs, le budget par env- ADR-06, pourquoi Postgres self-host plutôt que Neon