Déploiement complet et professionnel de l'infrastructure CAPBE sur Incus : un réseau privé avec accès internet, 2 stacks backend partagées (PostgreSQL+GIS / Oracle AI Database Free+GIS), Odoo 19 multi-tenant, des conteneurs projets/frontends, et un conteneur Caddy comme unique point d'entrée public (forward public créé manuellement dans la web UI Incus — aucune IP failover requise).
⚙️ Prérequis
- Serveur dédié OVH avec Incus installé et initialisé (
incus admin init) - Domaines déjà configurés chez OVH (pointent vers l'adresse d'écoute du forward) :
ci.capbe.ovh,*.ci.capbe.ovh,*.app.ci.capbe.ovh- Aucune IP failover requise : le forward public (
80/443 → caddy) est créé manuellement dans la web UI Incus juste après l'étape 03 (le playbook vérifie qu'il est en place avant de poursuivre — voir DEPLOY.md) - Oracle : télécharger le RPM
oracle-database-free-*.rpmdepuis oracle.com/database/free (compte gratuit) et le placer dansplaybook/downloads/ - Messagerie : jeton API OVH
OVH_AK/OVH_AS/OVH_CKcréé sur eu.api.ovh.com/createToken (GET/PUT/POST/DELETE sur/domain/zone/*) et renseigné dansconfig/secrets.env; port 25 sortant débloqué pour l'IP publique (espace client OVH) - ⚠️ Zone DNS OVH =
capbe.ovhet domaine mail =capbe.ovh(racine) — les apps vivent sousci(A ci,A *.ci…) mais les records de la messagerie sont à la RACINE (A mail,A webmail,A autoconfig,A autodiscover, MX/TXT/_dmarc/SRV sans préfixe) ;sudo ./publish-mail-dns.shpublie MX/SPF/DMARC/DKIM/SRV (idempotent)
🔒 Contraintes d'architecture (non négociables)
- Aucun port de l'hôte n'est sollicité : l'exposition publique passe
exclusivement par un network forward Incus (pur DNAT noyau nftables).
Aucun listener TCP sur l'hôte, aucun
proxy device(listen=tcp:0.0.0.0:...) — rien d'ouvert dansss -tln. - Aucun conteneur Docker n'est créé : tout tourne dans des conteneurs système Incus (LXC) avec des services systemd natifs (messagerie incluse : Stalwart Mail en binaire natif Rust, webmail SnappyMail en PHP). Docker peut rester installé et actif sur l'hôte pour d'autres projets — le playbook ne l'utilise jamais (le préflight émet un avertissement, pas un arrêt).
🚀 Ordre d'exécution (sur l'hôte, en root)
cd playbook
chmod +x *.sh lib/common.sh
sudo ./00-preflight.sh # vérifications + génération des secrets
sudo ./01-network.sh # réseau capbe-br0 (10.10.10.0/24, NAT)
sudo ./02-profiles.sh # profils ressources + réglages Oracle
sudo ./03-caddy.sh # Conteneur caddy + VÉRIFICATION du forward MANUEL (80/443 → caddy)
sudo ./04-pgis.sh # Stack #1 : PostgreSQL 18 + PostGIS + Prisma + SQLAlchemy + FastAPI
sudo ./05-oracle.sh # Stack #2 : Oracle Free + Spatial + TypeORM + SQLAlchemy + FastAPI
sudo ./06-odoo.sh # Odoo 19 multi-tenant (base = pgis)
sudo ./check-mail-prereqs.sh # (optionnel) prérequis de la messagerie avant l'étape 07
sudo ./07-mail.sh # Messagerie Stalwart : SMTP/IMAP/POP3/JMAP + webmail (backend pgis)
sudo ./08-frontend.sh projetA # un conteneur par projet (répéter au besoin)
sudo ./09-caddy-config.sh # Caddy : déploiement du Caddyfile complet (config finale)
sudo ./10-backup.sh --cron # sauvegardes quotidiennes (snapshots + dumps)
sudo ./smoke-test.sh # (post-09) validation complète : points d'entrée + messagerie + SRV + conformité
sudo ./12-migrate-projects.sh agrimeca=meca agroworoba=agro # (optionnel) migration des projets hérités — voir section dédiée
⚠️ Forward public = MANUEL : à l'étape 03, le playbook vérifie que le network forward (
80/443 → caddy, puis les ports mail) est en place avant de poursuivre — il ne le crée jamais. Les commandes exactes sont affichées.
> 📋 **Checklist détaillée** (prérequis, durées, vérifications, pièges) : voir
> [`DEPLOY.md`](DEPLOY.md).
> 📬 **Post-déploiement** (dernier point rouge PTR, mail-tester, sort des
> conteneurs héritage) : voir [`POST-DEPLOY.md`](POST-DEPLOY.md).
> 🧪 **Smoke-test** : `sudo ./smoke-test.sh` après l'étape 09 (points d'entrée
> publics, rendu webmail, JMAP, autoconfig/autodiscover, records SRV,
> conformité hôte).
> 📮 **Prérequis mail** : `sudo ./check-mail-prereqs.sh` avant l'étape 07.
> 🔒 **Forward manuel** : `80/443 → caddy` vérifié à l'étape 03 ; ports mail
> (`25/110/143/465/587/993/995/4190 → mail`) rappelés à l'étape 07.
> 🔒 **Sécurité des commits** : un hook git refuse tout secret dans le staging
> (activer sur un clone via `git config core.hooksPath .githooks`).
> 🧹 **Rollback** : `sudo ./11-teardown.sh` (destructeur — confirmation `OUI`,
> conserve sauvegardes et secrets, `--purge-secrets` pour tout effacer).
## 🧱 Architecture
Internet ── IP publique (adresse d'écoute du forward, manuelle)
│
[ capbe-caddy 10.10.10.x ] ← chef d'orchestre (TLS auto)
│
Réseau privé capbe-br0 (10.10.10.0/24) — NAT sortant
┌──────────┬──────────┬──────────┬──────────────┬──────────────────┐
▼ ▼ ▼ ▼ ▼ ▼
capbe-pgis capbe-oracle capbe-odoo capbe-mail capbe-projetA-front capbe-projetB-front 10.10.10.2 10.10.10.3 10.10.10.4 10.10.10.5 (par projet) Postgres18 OracleFree Odoo19 Stalwart Node.js / apps +PostGIS +Spatial multi-tenant SMTP/IMAP/JMAP ```
- Une seule IP publique (adresse d'écoute du forward, choisie manuellement dans la web UI Incus) — HTTP(S) via caddy (80/443) et messagerie via le forward (25/110/143/465/587/993/995/4190), tout en DNAT noyau (aucun port hôte ouvert).
- Aucune modification de la configuration réseau de l'hôte ni chez OVH : le routage est assuré dans Incus (network forward = DNAT noyau, aucun port hôte ouvert, aucun Docker).
- Tous les conteneurs partagent les 3 backends via le réseau privé.
📋 Tableau des conteneurs
| Conteneur | Image | Rôle | RAM | IP privée |
|---|---|---|---|---|
capbe-pgis |
Ubuntu 24.04 | PostgreSQL 18 + PostGIS 3.6 + Python/Node + Prisma/SQLAlchemy/FastAPI | 4 Go | 10.10.10.2 |
capbe-oracle |
Oracle Linux 9 | Oracle AI DB Free + Spatial (SDO_GEOMETRY) + Python/Node + TypeORM/SQLAlchemy/FastAPI | 6 Go | 10.10.10.3 |
capbe-odoo |
Ubuntu 24.04 | Odoo 19 multi-tenant (dbfilter, list_db=False) | 4 Go | 10.10.10.4 |
capbe-mail |
Ubuntu 24.04 | Messagerie Stalwart (SMTP/IMAP/POP3/JMAP/CalDAV/CardDAV) + webmail SnappyMail | 2 Go | 10.10.10.5 |
capbe-<projet>-front |
Ubuntu 24.04 | Frontend / application d'un projet | 2 Go | 10.10.10.x |
capbe-caddy |
Ubuntu 24.04 | Reverse proxy + TLS auto (HTTP-01) | 1 Go | 10.10.10.x (DHCP) |
🔌 Routage Caddy (Caddyfile)
odoo.ci.capbe.ovh→capbe-odoo:8069(multi-tenant : un tenant = une base)api.ci.capbe.ovh→capbe-pgis:8000(FastAPI Stack PostgreSQL)oracle-api.ci.capbe.ovh→capbe-oracle:8000(FastAPI Stack Oracle)webmail.capbe.ovh→capbe-mail:80(webmail SnappyMail) ;mail.capbe.ovh→ protocoles mail directs (SMTP/IMAP/POP3/JMAP sur IP dédiée) et/jmap*→capbe-mail:8080(JMAP Stalwart, trafic transitoire)autoconfig.capbe.ovh/autodiscover.capbe.ovh→capbe-mail:80(configuration auto des clients)<projet>.app.ci.capbe.ovh→capbe-<projet>-front:80(dynamique)
🔀 Migration de projets existants (étape 12)
./12-migrate-projects.sh <legacy>[=<id>] [<legacy>[=<id>]...] transforme un
conteneur « héritage » (pile complète autonome : Caddy local + FastAPI +
Postgres + Odoo) en architecture CAPBE :
- Frontend statique (build Flutter/JS) →
capbe-<id>(profil front, capbe-br0, nginx :80) →https://<id>.app.ci.capbe.ovh. Racine web détectée :/var/www/html(nginx) ou/usr/share/caddy(racine Caddy par défaut — cas agroworoba) - API FastAPI + sa base →
capbe-pgis(base<id>_api, rôleapp, propriété réattribuée viafix_owner+sweep_owner— DO blocks PG15+,REASSIGN OWNEDéchoue sur postgres). Code détecté dans/opt/<legacy>/apiou/app(layout supervisor/venv — cas agroworoba). Les API se répartissent sur les ports de pgis : 8000 pour la 1re, 8001, 8002… pour les suivantes (un port = une API). La 1re est servie surhttps://api.ci.capbe.ovh(rewrite/api/<res>→/api/v2/<res>) ; chaque API migrée est aussi servie en même-origine viahttps://<id>.app.ci.capbe.ovh/api/*→pgis:<port>(voir les blocsmeca.app.ci.capbe.ovh/agro.app.ci.capbe.ovhdu Caddyfile). Redis + worker Celery déployés automatiquement si l'app déclare uncelery_app - Odoo → tenant de
capbe-odoo(base<id>sur pgis, rôleodoo, dbfilter^%d$→https://<id>.ci.capbe.ovh) + copie du filestore. Si la base Odoo source n'a jamais été initialisée (pas deir_module_module), le script crée un tenant vierge (odoo -i base) pour une URL fonctionnelle — aucune donnée ERP à attendre dans ce cas
Exemple : sudo ./12-migrate-projects.sh agrimeca=meca agroworoba=agro
(conteneurs cibles capbe-meca / capbe-agro).
Le conteneur héritage n'est jamais touché (ni arrêté ni supprimé) : son
sort est décidé manuellement après validation des tests (en faisant tourner
les secrets). Limite : un seul service uvicorn par port sur capbe-pgis —
les API se répartissent sur 8000, 8001… (routage Caddy même-origine à
adapter dans config/Caddyfile).
🔐 Sécurité
- Firewall hôte OVH : n'ouvrir que
22,80,443,25,110,143,465,587,993,995,4190(le trafic atteint l'IP failover puis est routé par le forward — aucun port hôte n'écoute pour autant) - Bases jamais exposées publiquement : accès restreint au réseau
10.10.10.0/24(pg_hba.conf, listener Oracle interne) - Secrets générés dans
playbook/config/secrets.env(chmod 600) — à protéger - Sauvegardes quotidiennes : snapshots Incus +
pg_dumpall+ export Oracle
⚠️ Points d'attention Oracle
- Plafonds de l'édition Free : 2 vCPU, 2 Go SGA/PGA, 12 Go de données max
- Oracle Spatial & Graph (
SDO_GEOMETRY) inclus par défaut - RPM à télécharger manuellement (compte Oracle gratuit) →
playbook/downloads/ - Si l'installation LXC bloque sur
/dev/shmou les sysctls, bascule possible en VM Incus (voir05-oracle.sh— mêmes commandes sur image VM)