Date : 20/01/2026 Statut : PROPOSITION Priorité : HAUTE Version : 2.0 - Refonte complète
Un fichier conf.{branche}.gouroubleu.yml par branche de déploiement. Le nom du fichier = la branche, pas la destination. La destination et le type de déploiement sont définis à l'intérieur du fichier.
Principe clé : La branche déclenche, le fichier décide où et comment.
git push origin staging
↓
Daemon cherche: conf.staging.gouroubleu.yml
↓
Le fichier contient: target: "prod-portainer" (ou o2switch, ou nginx, ou win11...)
↓
Déploiement selon le target défini
Plus de .gitlab-ci.yml dans chaque projet.
| Push sur branche | Fichier cherché | Cas particulier |
|---|---|---|
main |
conf.prod.gouroubleu.yml |
Alias pour prod |
develop |
conf.dev.gouroubleu.yml |
Alias pour dev |
staging |
conf.staging.gouroubleu.yml |
- |
release |
conf.release.gouroubleu.yml |
- |
o2switch |
conf.o2switch.gouroubleu.yml |
- |
{n'importe} |
conf.{n'importe}.gouroubleu.yml |
- |
feature/* |
❌ Ignoré par défaut | Configurable |
hotfix/* |
❌ Ignoré par défaut | Configurable |
# conf.staging.gouroubleu.yml
# Peut déployer où on veut !
target: "prod-portainer" # → Docker sur prod
# OU
target: "dev-portainer" # → Docker sur dev
# OU
target: "o2switch" # → rsync vers O2switch
# OU
target: "nginx" # → fichiers sur nginx
# OU
target: "win11" # → scripts sur Windows
# OU
target: "pve" # → scripts/crons sur PVE
Projet API - 2 fichiers pour 2 environnements :
mon-api/
├── conf.prod.gouroubleu.yml # main → Docker prod-portainer
├── conf.dev.gouroubleu.yml # develop → Docker dev-portainer
└── src/
Projet site statique - déploie sur O2switch :
mon-site/
├── conf.prod.gouroubleu.yml # main → O2switch
└── dist/
Projet config nginx - déploie des configs :
nginx-configs/
├── conf.prod.gouroubleu.yml # main → fichiers sur nginx
└── sites/
Projet multi-cibles - même code, plusieurs destinations :
mon-projet/
├── conf.prod.gouroubleu.yml # main → Docker prod
├── conf.dev.gouroubleu.yml # develop → Docker dev
├── conf.staging.gouroubleu.yml # staging → Docker prod (autre config)
├── conf.o2switch.gouroubleu.yml # branche o2switch → static O2switch
└── src/
┌─────────────────────────────────────────────────────────────────────────────┐
│ DEPLOY-ORCHESTRATOR (sur prod-portainer) │
│ │
│ GITLAB DAEMON │
│ ────── ────── │
│ push branche X ──────────► 1. Webhook reçu │
│ 2. Clone/pull repo │
│ 3. Cherche conf.X.gouroubleu.yml │
│ 4. Si absent → STOP (pas de deploy auto) │
│ 5. Parse config │
│ 6. Lit "target" → détermine stratégie │
│ │ │
│ ┌──────────────────┼──────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ DOCKER │ │ FILES │ │ REMOTE │ │
│ │ │ │ │ │ │ │
│ │ build │ │ rsync │ │ rsync │ │
│ │ push │ │ ssh cmd │ │ git push │ │
│ │ deploy │ │ symlinks │ │ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │prod-portainer│ │nginx/pve/ │ │ o2switch │ │
│ │dev-portainer │ │win11/gitlab │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
name: "mon-service" # Nom unique du service
target: "prod-portainer" # OÙ déployer (obligatoire)
C'est tout ! Le reste est optionnel selon le type de target.
| Target | Type | Options disponibles |
|---|---|---|
prod-portainer |
Docker | type, port, domain, services, storage, crons, logs |
dev-portainer |
Docker | type, port, domain, services, storage, crons, logs |
o2switch |
Remote | method, source, dest, domain |
nginx |
Files | files, links, pre/post_deploy |
win11 |
Files | files, pre/post_deploy |
pve |
Files | files, pre/post_deploy |
gitlab |
Files | files, pre/post_deploy |
{custom} |
Configurable | Selon machines-registry.json |
# conf.prod.gouroubleu.yml - Service Docker
name: "mon-api"
target: "prod-portainer" # Docker sur prod
type: "node" # node | bun | python | rust | static | custom
port: 3000
# Exposition (optionnel)
domain: "@" # → mon-api.33800.nowhere84.com
websocket: true
# Services stack (optionnel)
supabase: true
redis: true
ntfy: true
# Stockage (optionnel)
storage:
data: true
uploads: true
# Crons (optionnel)
crons:
- name: "cleanup"
schedule: "0 3 * * *"
command: "npm run cleanup"
# Observabilité (optionnel)
logs: true
health: "/health"
# conf.prod.gouroubleu.yml - Site sur O2switch
name: "mon-site"
target: "o2switch"
method: "rsync" # rsync | git
source: "dist/" # Dossier à déployer
dest: "public_html/mon-site" # Chemin sur O2switch
domain: "mon-site.nowhere84.com" # Sous-domaine
# conf.prod.gouroubleu.yml - Config Nginx
name: "api-gateway-config"
target: "nginx"
files:
- source: "nginx/*.conf"
dest: "configs/"
links:
- source: "configs/api-gateway.conf"
dest: "/etc/nginx/sites-enabled/api-gateway.conf"
post_deploy:
- "sudo nginx -t && sudo systemctl reload nginx"
keep_releases: 5
rollback_on_failure: true
# conf.prod.gouroubleu.yml - Script sur Win11
name: "mon-outil-ia"
target: "win11"
files:
- source: "scripts/*.py"
dest: "scripts/"
- source: "models/"
dest: "models/"
shared:
- "outputs/"
- "cache/"
post_deploy:
- "python scripts/setup.py"
keep_releases: 3
# conf.prod.gouroubleu.yml - Cron système
name: "backup-script"
target: "pve"
files:
- source: "scripts/backup.sh"
dest: "scripts/"
mode: "0755"
- source: "cron/backup-cron"
dest: "/etc/cron.d/backup-cron"
mode: "0644"
post_deploy:
- "chmod 644 /etc/cron.d/backup-cron"
Le champ domain est disponible pour tous les types de targets et génère automatiquement la config nginx correspondante.
| Target | domain | Action du daemon |
|---|---|---|
prod-portainer |
domain: "api" |
Génère nginx proxy → container:port |
dev-portainer |
domain: "api-dev" |
Génère nginx proxy → container:port |
nginx (static) |
domain: "site" |
Génère nginx serve → /current/ path |
o2switch |
domain: "site.nowhere84.com" |
Configure sous-domaine O2switch |
win11 |
domain: "ollama" |
Génère nginx proxy → win11:port |
pve |
domain: "monitoring" |
Génère nginx proxy → pve:port |
Syntaxe domaine :
# ══════════════════════════════════════════════════════════════════════
# RACCOURCIS (défaut = stack 33800)
# ══════════════════════════════════════════════════════════════════════
domain: "@" # → {name}.33800.nowhere84.com
domain: "api" # → api.33800.nowhere84.com
# ══════════════════════════════════════════════════════════════════════
# DOMAINES COMPLETS - N'IMPORTE QUOI SUR nowhere84.com
# ══════════════════════════════════════════════════════════════════════
# Sous-domaines racine (premier niveau)
domain: "montruc.nowhere84.com"
domain: "dashboard.nowhere84.com"
domain: "api.nowhere84.com"
domain: "random.nowhere84.com"
# Sous-domaines stack 33800
domain: "api.33800.nowhere84.com"
domain: "deep.33800.nowhere84.com"
# Sous-domaines stack 86000
domain: "app.86000.nowhere84.com"
# Sous-sous-domaines (profondeur illimitée)
domain: "v2.api.33800.nowhere84.com"
domain: "dev.app.projet.nowhere84.com"
domain: "a.b.c.d.nowhere84.com"
# ══════════════════════════════════════════════════════════════════════
# DOMAINES EXTERNES (autres que nowhere84.com)
# ══════════════════════════════════════════════════════════════════════
domain: "api.monsite.com" # → DNS externe (config manuelle)
domain: "app.autredomaine.fr"
# Note: Pour les domaines externes, configurer le DNS manuellement
# pour pointer vers 82.65.119.221
Logique de résolution :
domain: "xxx"
│
├─ Est "@" ?
│ └─ OUI → {name}.33800.nowhere84.com
│
├─ Contient "." ?
│ ├─ NON → Raccourci : xxx.33800.nowhere84.com
│ └─ OUI → Domaine complet tel quel
│
└─ Fin contient ".nowhere84.com" ?
├─ OUI → DNS auto via O2switch API
└─ NON → DNS manuel (domaine externe)
Gestion DNS automatique (nowhere84.com) :
Le daemon crée automatiquement les wildcards nécessaires via l'API O2switch :
| Domaine demandé | Wildcard créé |
|---|---|
api.33800.nowhere84.com |
✅ *.33800 existe |
v2.api.33800.nowhere84.com |
🆕 *.api.33800 créé |
montruc.nowhere84.com |
✅ * racine existe |
deep.montruc.nowhere84.com |
🆕 *.montruc créé |
a.b.c.nowhere84.com |
🆕 *.b.c créé |
Exemples concrets :
# Service Docker exposé
name: "mon-api"
target: "prod-portainer"
type: "node"
port: 3000
domain: "api" # → api.33800.nowhere84.com → container:3000
# Site statique sur nginx (pas Docker)
name: "mon-site-static"
target: "nginx"
files:
- source: "dist/"
dest: "public/"
domain: "blog" # → blog.33800.nowhere84.com → /current/public/
# Service sur win11 exposé via nginx
name: "ollama-proxy"
target: "win11"
port: 11434
domain: "ollama" # → ollama.33800.nowhere84.com → win11:11434
# Service sur PVE exposé
name: "deploy-orchestrator"
target: "pve"
port: 9500
domain: "deploy" # → deploy.33800.nowhere84.com → pve:9500
# conf.prod.gouroubleu.yml
# ═══════════════════════════════════════════════════════════════════════════
# CONFIGURATION DÉPLOIEMENT - Stack 33800
# ═══════════════════════════════════════════════════════════════════════════
# ───────────────────────────────────────────────────────────────────────────
# OBLIGATOIRE
# ───────────────────────────────────────────────────────────────────────────
name: "mon-service" # Nom unique du service
target: "prod-portainer" # Où déployer
type: "node" # node | bun | python | rust | static | custom
# ───────────────────────────────────────────────────────────────────────────
# EXPOSITION (optionnel)
# ───────────────────────────────────────────────────────────────────────────
port: 3000 # Port d'écoute du service
domain: "@" # → {name}.33800.nowhere84.com
# ou
domain: "api" # → api.33800.nowhere84.com
# ou
domain: "api.monprojet" # → api.monprojet.33800.nowhere84.com
# (wildcard DNS créé automatiquement)
# ou
domain: "custom.86000.nowhere84.com" # Autre stack/domaine
websocket: true # Active proxy WebSocket dans Nginx
# ───────────────────────────────────────────────────────────────────────────
# SERVICES STACK (optionnel - déclare uniquement ce dont tu as besoin)
# ───────────────────────────────────────────────────────────────────────────
# Base de données / Cache
supabase: true # → DATABASE_URL, SUPABASE_URL, SUPABASE_KEY
redis: true # → REDIS_URL
# Notifications
ntfy: true # → NTFY_URL (+ notif deploy auto)
apprise: true # → APPRISE_URL (multi-canal)
# Authentification
auth: true # → AUTH_URL, AUTH_KEY
# Connecteurs externes
linkedin: true # → CONNECTORS_URL
# IA via ai-orchestrator (queue Redis + switch auto GPU)
ollama: true # LLM
comfyui: true # Image generation avancée
fooocus: true # Image generation simple
bark: true # TTS avec émotions
musicgen: true # Génération musique
wan21: true # Text to video
triposr: true # Image to 3D
applio: true # Voice cloning RVC
facefusion: true # Face swap
sadtalker: true # Talking head animation
# APIs Médias
whisper: true # → WHISPER_URL (transcription)
yolo: true # → YOLO_URL (détection objets)
ffmpeg: true # → FFMPEG_URL (traitement vidéo)
# ───────────────────────────────────────────────────────────────────────────
# STOCKAGE (optionnel)
# ───────────────────────────────────────────────────────────────────────────
storage:
# Dossiers auto-créés et montés
data: true # /app/data → /stock_Xto/services/{name}/data/
uploads: true # /app/uploads → /stock_Xto/services/{name}/uploads/
# Accès médias partagés (lecture seule)
media: true # /media → /stock_36to/data/ (ro)
# Backup automatique vers O2switch
backup: true # Sync quotidien → O2switch
# Volumes custom
volumes:
- source: "stock_8to/ai-data/models"
target: "/models"
readonly: true
- source: "stock_36to/data/films"
target: "/films"
readonly: true
- source: "o2switch/static/{name}"
target: "/static"
# ───────────────────────────────────────────────────────────────────────────
# CRONS (optionnel)
# ───────────────────────────────────────────────────────────────────────────
crons:
- name: "collect-data"
schedule: "0 */6 * * *" # Cron expression
command: "npm run collect" # Commande à exécuter
description: "Collecte données" # Description pour dashboard
timeout: 1800 # Timeout en secondes (défaut: 3600)
retry: 3 # Retry si échec (défaut: 0)
notify: true # Notifier via ntfy
notify_on: "failure" # always | success | failure
enabled: true # Actif ou non (défaut: true)
- name: "cleanup"
schedule: "0 3 * * *"
command: "npm run cleanup"
description: "Nettoyage données anciennes"
# ───────────────────────────────────────────────────────────────────────────
# OBSERVABILITÉ (optionnel)
# ───────────────────────────────────────────────────────────────────────────
logs: true # Logs vers Loki
# ou avec options:
logs:
type: "api" # Label type (api | app | worker | cron)
level: "info" # Niveau minimum
health: "/health" # Endpoint health check pour monitoring
# → Prometheus scrape auto
# ───────────────────────────────────────────────────────────────────────────
# CONFIGURATION CUSTOM (optionnel)
# ───────────────────────────────────────────────────────────────────────────
env: # Variables d'environnement custom
NODE_ENV: "production"
CUSTOM_VAR: "value"
secrets: # Référence secrets centraux
- API_KEY # → injecte depuis /secrets/API_KEY
- EXTERNAL_TOKEN
# ───────────────────────────────────────────────────────────────────────────
# OPTIONS AVANCÉES (optionnel)
# ───────────────────────────────────────────────────────────────────────────
replicas: 1 # Nombre d'instances (défaut: 1)
memory: "512m" # Limite mémoire
cpu: "0.5" # Limite CPU
dockerfile: "Dockerfile.custom" # Dockerfile custom (si type: custom)
nginx: # Options Nginx avancées
rate_limit: "10r/s"
client_max_body: "100m"
proxy_timeout: 300
Toutes les cibles de déploiement sont définies dans un registre central :
// /stock_8to/33800-stack/monitoring/machines-registry.json
{
"machines": {
"prod-portainer": {
"ip": "192.168.1.12",
"type": "docker",
"ssh_user": "gouroubleu",
"services_path": "/mnt/stock_8to/33800-stack/services",
"docker_data_root": "/mnt/stock_8to/33800-stack/docker/prod"
},
"dev-portainer": {
"ip": "192.168.1.51",
"type": "docker",
"ssh_user": "gouroubleu",
"services_path": "/mnt/stock_1to/services",
"docker_data_root": "/mnt/stock_1to/docker/dev"
},
"o2switch": {
"host": "yellow.o2switch.net",
"type": "remote",
"ssh_user": "deas8499",
"services_path": "~/services",
"methods": ["rsync", "git"]
},
"nginx": {
"ip": "192.168.1.104",
"type": "files",
"ssh_user": "gouroubleu",
"services_path": "/opt/33800-services"
},
"win11": {
"ip": "192.168.1.30",
"type": "files",
"ssh_user": "gouro",
"services_path": "I:/33800-services"
},
"pve": {
"ip": "192.168.1.4",
"type": "local",
"services_path": "/stock_8to/33800-stack/services"
},
"gitlab": {
"ip": "192.168.1.196",
"type": "files",
"ssh_user": "gouroubleu",
"services_path": "/opt/33800-services"
},
"jellyfin": {
"ip": "192.168.1.199",
"type": "files",
"ssh_user": "gouroubleu",
"services_path": "/opt/33800-services"
},
"vscode": {
"ip": "192.168.1.154",
"type": "files",
"ssh_user": "gouroubleu",
"services_path": "/opt/33800-services"
}
}
}
| Type | Stratégie | Machines |
|---|---|---|
docker |
Build → Registry → Container | prod-portainer, dev-portainer |
files |
Releases + Symlinks | nginx, win11, pve, gitlab, etc. |
remote |
rsync/git vers serveur externe | o2switch |
local |
Copie directe (même machine) | pve |
Pour les targets de type files, chaque service déployé suit cette structure :
{services_path}/
└── {service-name}/
├── current -> releases/2026-01-20_19-30-abc123/ # Symlink version active
├── releases/
│ ├── 2026-01-20_19-30-abc123/ # Version actuelle
│ ├── 2026-01-20_18-00-def456/ # Version précédente
│ └── 2026-01-20_12-00-ghi789/ # Ancienne version
├── shared/ # Fichiers persistants entre releases
│ ├── logs/
│ ├── cache/
│ └── data/
└── .deploy-history.json # Historique local
Exemples concrets :
# Sur nginx (192.168.1.104)
/opt/33800-services/
└── api-gateway-config/
├── current -> releases/2026-01-20_19-30/
├── releases/
│ └── 2026-01-20_19-30/
│ └── configs/
│ └── api-gateway.conf
└── shared/
# Sur win11 (I:/)
I:/33800-services/
└── mon-outil-ia/
├── current -> releases/2026-01-20_19-30/
├── releases/
│ └── 2026-01-20_19-30/
│ ├── scripts/
│ └── models/
└── shared/
├── outputs/
└── cache/
1. PREPARE
└─ timestamp = "2026-01-20_19-30"
└─ release_path = {services_path}/{name}/releases/{timestamp}-{commit_short}/
└─ mkdir -p {release_path}
2. DEPLOY FILES
└─ rsync fichiers source → {release_path}/
└─ chmod selon config (mode: "0755")
3. LINK SHARED
└─ Pour chaque dossier dans shared:
└─ mkdir -p {services_path}/{name}/shared/{dir}
└─ ln -sf ../../shared/{dir} {release_path}/{dir}
4. PRE-DEPLOY (optionnel)
└─ Exécute commandes pre_deploy
└─ Si échec → annule et notifie
5. SWITCH (atomique)
└─ old_release = readlink(current)
└─ ln -sfn {release_path} {services_path}/{name}/current
└─ (atomique car ln -sfn)
6. POST-DEPLOY
└─ Exécute commandes post_deploy
└─ Si échec ET rollback_on_failure → rollback
7. REGISTER
└─ Met à jour .deploy-history.json
└─ Met à jour services-registry.json global
8. CLEANUP
└─ Garde les N dernières releases (keep_releases, défaut: 5)
└─ rm -rf anciennes releases
9. NOTIFY
└─ POST ntfy avec status
Automatique (si rollback_on_failure: true) :
Si post_deploy échoue:
└─ ln -sfn {old_release} current
└─ Notifie "Rollback automatique"
Manuel via API :
# Rollback à la version précédente
curl -X POST "http://pve.local:9500/api/services/{name}/rollback"
# Rollback à une version spécifique
curl -X POST "http://pve.local:9500/api/services/{name}/rollback?to=2026-01-20_18-00"
# Liste des releases disponibles
curl "http://pve.local:9500/api/services/{name}/releases"
{
"service": "api-gateway-config",
"target": "nginx",
"current_release": "2026-01-20_19-30-abc123",
"deploys": [
{
"release": "2026-01-20_19-30-abc123",
"branch": "main",
"commit": "abc123def456",
"deployed_at": "2026-01-20T19:30:00Z",
"deployed_by": "deploy-orchestrator",
"status": "success",
"duration_ms": 2500
},
{
"release": "2026-01-20_18-00-def456",
"branch": "main",
"commit": "def456ghi789",
"deployed_at": "2026-01-20T18:00:00Z",
"deployed_by": "deploy-orchestrator",
"status": "success",
"duration_ms": 2100
}
]
}
| Service | URL | Méthode actuelle | À migrer ? |
|---|---|---|---|
| Dashboard | dashboard.nowhere84.com | git push (cron) | Optionnel |
| Claude Viewer | claude.nowhere84.com | rsync (cron) | Optionnel |
| Status | status.nowhere84.com | rsync (cron) | Optionnel |
Ces services fonctionnent déjà. Ils peuvent rester gérés par cron ou être migrés vers le daemon (conf.o2switch.gouroubleu.yml).
Nginx est géré comme un projet GitLab standard :
nginx-configs/ # Repo GitLab
├── conf.prod.gouroubleu.yml # Config deploy-orchestrator
├── sites/
│ ├── api.33800.conf
│ ├── dashboard.33800.conf
│ └── ...
├── scripts/
│ └── check-certs.sh # Auto-génère les certs manquants
└── templates/
└── proxy.conf.j2 # Template pour nouveaux services
1. Tu déploies mon-api avec domain: "api.33800.nowhere84.com"
2. Deploy-orchestrator (PVE) :
├─ Déploie le container sur prod-portainer
├─ Crée wildcard DNS si besoin (O2switch API)
├─ Génère config nginx depuis template
└─ Commit + push vers nginx-configs (GitLab)
3. GitLab webhook → Deploy-orchestrator
4. Deploy-orchestrator déploie nginx-configs :
├─ rsync sites/*.conf → nginx
└─ Exécute post_deploy sur nginx
5. post_deploy (sur nginx) :
├─ check-certs.sh (génère certs manquants)
└─ nginx -t && systemctl reload nginx
name: "nginx-configs"
target: "nginx"
files:
- source: "sites/*.conf"
dest: "sites/"
- source: "scripts/*.sh"
dest: "scripts/"
mode: "0755"
links:
- source: "sites/*.conf"
dest: "/etc/nginx/sites-enabled/"
post_deploy:
- "/opt/33800-services/nginx-configs/current/scripts/check-certs.sh"
- "sudo nginx -t && sudo systemctl reload nginx"
keep_releases: 5
rollback_on_failure: true
#!/bin/bash
# Vérifie et génère les certificats manquants
SITES_DIR="/etc/nginx/sites-enabled"
CERTBOT_EMAIL="gouroubleu@gmail.com"
# Extraire tous les server_name des configs
domains=$(grep -rh "server_name" $SITES_DIR/*.conf | \
sed 's/server_name//g; s/;//g' | \
tr ' ' '\n' | \
grep -v "^$" | \
sort -u)
for domain in $domains; do
# Skip si cert existe déjà
if [ -f "/etc/letsencrypt/live/$domain/fullchain.pem" ]; then
continue
fi
# Wildcard ? (*.xxx.nowhere84.com)
if [[ "$domain" == *"*"* ]]; then
# DNS-01 challenge pour wildcard
certbot certonly --dns-01 \
--dns-01-credentials /etc/letsencrypt/o2switch-credentials.ini \
-d "$domain" \
--email $CERTBOT_EMAIL \
--agree-tos --non-interactive
else
# HTTP-01 challenge pour domaine simple
certbot certonly --webroot \
-w /var/www/certbot \
-d "$domain" \
--email $CERTBOT_EMAIL \
--agree-tos --non-interactive
fi
done
echo "Certificats vérifiés/générés"
| Quoi | Où | Comment |
|---|---|---|
| DNS wildcard | O2switch | API depuis deploy-orchestrator (PVE) |
| Configs nginx | GitLab → nginx | Push + deploy standard |
| Certificats SSL | nginx | Script post_deploy local |
| Reload nginx | nginx | post_deploy |
Avantages :
Chaque service peut être configuré de deux façons :
# Simple (valeurs par défaut)
supabase: true
# Détaillé (avec options)
supabase:
enabled: true
schema: "mon_schema"
migrations: true
supabase:
enabled: true
# Base de données
database: "mon_app" # Nom BDD (défaut: nom du service)
schema: "public" # Schema (défaut: public)
# Migrations
migrations: true # Auto-run migrations au deploy
migrations_path: "db/migrations" # Chemin fichiers migration
# Accès
role: "service" # anon | authenticated | service
# Storage (buckets)
storage:
- name: "uploads"
public: false
- name: "avatars"
public: true
# Variables injectées:
# DATABASE_URL, SUPABASE_URL, SUPABASE_KEY, SUPABASE_SERVICE_KEY
# SUPABASE_DB_NAME, SUPABASE_SCHEMA
redis:
enabled: true
db: 0 # Database number (0-15)
prefix: "myapp:" # Préfixe clés (isolation)
# Optionnel: Redis dédié
dedicated: false # true = instance Redis séparée
# Variables injectées:
# REDIS_URL, REDIS_DB, REDIS_PREFIX
# Outils IA (passent par ai-orchestrator qui gère la queue GPU)
ollama:
enabled: true
model: "llama3.2" # Modèle par défaut
priority: "normal" # low | normal | high | critical
comfyui:
enabled: true
workflow: "default" # Workflow par défaut
priority: "normal"
fooocus:
enabled: true
preset: "quality" # speed | quality | extreme
priority: "normal"
wan21:
enabled: true
priority: "low" # Vidéo = gourmand, priorité basse
bark:
enabled: true
voice: "default"
whisper:
enabled: true
model: "large-v3" # tiny | base | small | medium | large-v3
language: "fr"
# Variables injectées pour chaque outil:
# ORCHESTRATOR_URL, {TOOL}_ENABLED=true, {TOOL}_MODEL, {TOOL}_PRIORITY
ntfy:
enabled: true
topic: "mon-service" # Topic dédié (défaut: nom service)
priority: "default" # min | low | default | high | urgent
apprise:
enabled: true
channels: # Canaux à utiliser
- "ntfy"
- "email"
- "slack"
# Variables injectées:
# NTFY_URL, NTFY_TOPIC, APPRISE_URL, APPRISE_CHANNELS
linkedin:
enabled: true
scopes: # Permissions demandées
- "r_liteprofile"
- "r_emailaddress"
- "w_member_social"
# Variables injectées:
# CONNECTORS_URL, BROWSER_CONNECTOR_URL, LINKEDIN_SCOPES
| Service | Options principales | Variables injectées |
|---|---|---|
supabase |
database, schema, migrations, storage | DATABASEURL, SUPABASE* |
redis |
db, prefix, dedicated | REDISURL, REDIS* |
ollama |
model, priority | ORCHESTRATORURL, OLLAMA* |
comfyui |
workflow, priority | ORCHESTRATORURL, COMFYUI* |
fooocus |
preset, priority | ORCHESTRATORURL, FOOOCUS* |
wan21 |
priority | ORCHESTRATORURL, WAN21* |
bark |
voice | ORCHESTRATORURL, BARK* |
whisper |
model, language | ORCHESTRATORURL, WHISPER* |
ntfy |
topic, priority | NTFY_URL, NTFY_TOPIC |
apprise |
channels | APPRISE_URL, APPRISE_CHANNELS |
linkedin |
scopes | CONNECTORSURL, LINKEDIN* |
Container Docker sur prod-portainer qui :
/mnt/stock_8to/33800-stack/projects/deploy-orchestrator/
├── app/ # Code Python FastAPI
├── static/ # Dashboard frontend (SPA)
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── conf.prod.gouroubleu.yml # Sa propre config !
docker-compose.yml :
services:
deploy-orchestrator:
build: .
container_name: deploy-orchestrator
restart: unless-stopped
ports:
- "9500:9500"
volumes:
- /mnt/stock_8to/33800-stack/deploy-orchestrator/data:/app/data
- /mnt/stock_8to/33800-stack/monitoring:/app/monitoring:ro
- /var/run/docker.sock:/var/run/docker.sock
environment:
- GITLAB_URL=https://gitlab.33800.nowhere84.com
- GITLAB_TOKEN=${GITLAB_TOKEN}
- REGISTRY_URL=registry.33800.nowhere84.com
- NTFY_URL=http://ntfy:80
networks:
- notifications_default
logging:
driver: loki
options:
loki-url: "http://192.168.1.12:3100/loki/api/v1/push"
labels: "env,host,type,service"
| Élément | Valeur |
|---|---|
| API | http://192.168.1.12:9500/api/ |
| Dashboard | http://192.168.1.12:9500/ |
| Domaine | deploy.33800.nowhere84.com |
| WebSocket | ws://192.168.1.12:9500/ws/ |
Le dashboard est intégré au service (servi par FastAPI depuis /static/).
| Feature | Description |
|---|---|
| Déploiements live | Liste des déploiements en cours (build, push, deploy) |
| Queue | Déploiements en attente |
| Logs | Streaming logs en temps réel (WebSocket) |
| Historique | Déploiements passés (succès/échecs, durée, rollbacks) |
| Services | Liste services déployés par target/machine |
| Actions | Cancel deploy, rollback, redeploy |
┌─────────────────────────────────────────────────────────────────┐
│ deploy-dashboard │
│ (deploy.33800.nowhere84.com) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Deploys │ │ Queue │ │ Services │ │
│ │ en cours │ │ (pending) │ │ déployés │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ │ WebSocket │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Logs Stream │ │
│ │ [19:30:01] Webhook received: mon-api (main) │ │
│ │ [19:30:02] Cloning repository... │ │
│ │ [19:30:05] Building Docker image... │ │
│ │ [19:30:45] Pushing to registry... │ │
│ │ [19:31:00] Deploying to prod-portainer... │ │
│ │ [19:31:10] ✅ Deploy successful │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
│ REST + WebSocket
▼
┌─────────────────────────────────────────────────────────────────┐
│ deploy-orchestrator:9500 (prod-portainer) │
│ │
│ GET /api/deploys → Liste déploiements │
│ GET /api/deploys/{id} → Détail + logs │
│ POST /api/deploys/{id}/cancel → Annuler │
│ POST /api/deploys/{id}/retry → Relancer │
│ GET /api/services → Services déployés │
│ POST /api/services/{name}/rollback → Rollback │
│ WS /ws/deploys → Stream deploys temps réel │
│ WS /ws/logs/{id} → Stream logs d'un deploy │
└─────────────────────────────────────────────────────────────────┘
Vue Deploys :
┌────────────────────────────────────────────────────────────────┐
│ Déploiements en cours │
├────────────────────────────────────────────────────────────────┤
│ 🔄 mon-api (main) → prod-portainer [Building... 45%] │
│ 🔄 mon-site (main) → o2switch [Syncing...] │
├────────────────────────────────────────────────────────────────┤
│ En attente (2) │
├────────────────────────────────────────────────────────────────┤
│ ⏳ autre-api (develop) → dev-portainer │
│ ⏳ config-nginx (main) → nginx │
└────────────────────────────────────────────────────────────────┘
Vue Services :
┌────────────────────────────────────────────────────────────────┐
│ Services par target │
├────────────────────────────────────────────────────────────────┤
│ prod-portainer (12 services) │
│ ├─ mon-api v1.2.3 ✅ running [Rollback] [Logs] │
│ ├─ notif-logger v2.0.0 ✅ running [Rollback] [Logs] │
│ └─ ... │
│ │
│ nginx (3 configs) │
│ ├─ api-gateway 2026-01-20 ✅ active [Rollback] │
│ └─ ... │
│ │
│ o2switch (2 sites) │
│ ├─ dashboard 2026-01-20 ✅ synced │
│ └─ ... │
└────────────────────────────────────────────────────────────────┘
| Variable | PROD | DEV |
|---|---|---|
DATABASE_URL |
postgresql://...@192.168.1.12:5433/postgres |
postgresql://...@192.168.1.51:5432/postgres |
SUPABASE_URL |
http://192.168.1.12:8200 |
http://192.168.1.51:8100 |
REDIS_URL |
redis://192.168.1.12:6379 |
redis://192.168.1.51:6379 |
ORCHESTRATOR_URL |
http://192.168.1.12:5501 |
http://192.168.1.12:5501 (même) |
NTFY_URL |
http://192.168.1.12:8080 |
http://192.168.1.12:8080 (même) |
*.33800.nowhere84.com → 82.65.119.221 ✅
*.86000.nowhere84.com → (à configurer)
*.nowhere84.com → O2switch
DOMAIN DEMANDÉ WILDCARD NÉCESSAIRE ACTION
──────────────────────────────────────────────────────────────────────────
api.33800.nowhere84.com *.33800.nowhere84.com ✅ Existe
foo.bar.33800.nowhere84.com *.bar.33800.nowhere84.com 🆕 Créer
x.y.z.33800.nowhere84.com *.y.z.33800.nowhere84.com 🆕 Créer
1. Parse domaine : "api.monprojet.33800.nowhere84.com"
2. Extrait wildcard nécessaire : "*.monprojet.33800.nowhere84.com"
3. Vérifie cache local → existe ?
└─ Non → Query DNS → existe ?
└─ Non → Créer via API O2switch
4. Créer entrée DNS :
└─ Type: A
└─ Name: *.monprojet.33800
└─ Value: 82.65.119.221
└─ TTL: 3600
5. Attendre propagation (ou skip si déjà créé)
6. Met à jour cache local
/stock_8to/33800-stack/monitoring/dns-wildcards.json
{
"updated": "2026-01-20T18:00:00Z",
"wildcards": {
"*.33800.nowhere84.com": {
"ip": "82.65.119.221",
"created": "2025-01-01T00:00:00Z",
"source": "manual"
},
"*.monprojet.33800.nowhere84.com": {
"ip": "82.65.119.221",
"created": "2026-01-20T18:30:00Z",
"source": "deploy-orchestrator"
}
}
}
| Clé | PROD (prod-portainer) | DEV (dev-portainer) |
|---|---|---|
data: true |
/mnt/stock_8to/services/{name}/data |
/mnt/stock_1to/services/{name}/data |
uploads: true |
/mnt/stock_8to/services/{name}/uploads |
/mnt/stock_1to/services/{name}/uploads |
media: true |
/mnt/stock_36to/data/ (ro) |
/mnt/stock_36to/data/ (ro) |
backup: true |
→ O2switch ~/services/{name}/ | → O2switch ~/services/{name}/ |
| Préfixe source | Résolution |
|---|---|
local/... |
stock_8to (PROD) ou stock_1to (DEV) |
stock_8to/... |
/mnt/stock_8to/... (PROD only) |
stock_1to/... |
/mnt/stock_1to/... (DEV only) |
stock_36to/... |
/mnt/stock_36to/... (les deux) |
o2switch/... |
Sync via rclone/rsync |
# Lors du deploy, le daemon crée si nécessaire :
mkdir -p /mnt/stock_8to/services/{name}/data
mkdir -p /mnt/stock_8to/services/{name}/uploads
chown -R 1000:1000 /mnt/stock_8to/services/{name}
/stock_8to/33800-stack/monitoring/crons-registry.json
{
"updated": "2026-01-20T18:00:00Z",
"services": {
"needfinder": {
"env": "dev",
"host": "dev-portainer",
"container": "needfinder-collector-dev",
"crons": [
{
"name": "collect-reddit",
"schedule": "0 */6 * * *",
"command": "npm run collect:reddit",
"description": "Collecte posts Reddit",
"timeout": 1800,
"enabled": true,
"last_run": "2026-01-20T12:00:00Z",
"last_status": "success",
"last_duration": 45,
"next_run": "2026-01-20T18:00:00Z"
}
]
}
},
"system": [
{
"host": "pve",
"name": "rsync-backup",
"schedule": "0 * * * *",
"description": "Sync stock_8to → stock_36to",
"source": "/etc/cron.d/rsync-stock8-to-stock36"
},
{
"host": "pve",
"name": "monitoring",
"schedule": "0 * * * *",
"description": "Collecte monitoring dashboard",
"source": "/etc/cron.d/monitoring-33800"
},
{
"host": "gitlab",
"name": "gitlab-backup",
"schedule": "0 */6 * * *",
"description": "Backup GitLab",
"source": "/etc/cron.d/gitlab-backup"
}
]
}
Option A : Cron sur l'hôte (recommandé)
# Généré par daemon dans /etc/cron.d/{name}-crons
# needfinder-crons
0 */6 * * * root /usr/local/bin/run-container-cron.sh needfinder collect-reddit "npm run collect:reddit" >> /var/log/crons/needfinder.log 2>&1
Option B : Cron interne (node-cron, celery, etc.)
Le daemon génère scheduler.json monté dans le container.
#!/bin/bash
# /usr/local/bin/run-container-cron.sh
CONTAINER=$1
CRON_NAME=$2
COMMAND=$3
START=$(date +%s)
docker exec $CONTAINER $COMMAND
EXIT_CODE=$?
END=$(date +%s)
DURATION=$((END - START))
# Met à jour crons-registry.json
curl -X POST "http://localhost:9500/api/crons/update" \
-H "Content-Type: application/json" \
-d "{\"container\":\"$CONTAINER\",\"cron\":\"$CRON_NAME\",\"status\":\"$EXIT_CODE\",\"duration\":$DURATION}"
# Notifie si échec
if [ $EXIT_CODE -ne 0 ]; then
curl -X POST "http://192.168.1.12:5300/api/notify/push" \
-d "{\"title\":\"❌ Cron failed: $CRON_NAME\",\"body\":\"$CONTAINER - exit $EXIT_CODE\"}"
fi
| Critère | Container Docker (choisi) | PVE direct |
|---|---|---|
| Isolation | ✅ Container isolé | ⚠️ Pollue l'hyperviseur |
| Cohérence | ✅ Comme les autres services | ❌ Exception |
| Accès stockage | ✅ Via NFS (suffisant) | ✅ Direct ZFS |
| Docker socket | ✅ Monté dans le container | ✅ Natif |
| Rebuild/Update | ✅ docker compose up --build | ⚠️ Manuel |
| Logs Loki | ✅ Intégré automatiquement | ⚠️ Config manuelle |
| CI/CD | ✅ Peut se déployer lui-même | ❌ Hors circuit |
Choix : Container Docker sur prod-portainer
Le daemon en container peut :
┌─────────────────────────────────────────────────────────────────────────────┐
│ prod-portainer (192.168.1.12) │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ DEPLOY-ORCHESTRATOR │ │ AI-ORCHESTRATOR │ │ NTFY + LOGS │ │
│ │ (port 9500) │◄───►│ (port 5501) │ │ (port 8080) │ │
│ └────────┬────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │
│ │ Docker socket + NFS │
│ │ │
│ ┌────────┴────────────────────────────────────────────────────┐ │
│ │ /mnt/stock_8to (NFS depuis PVE) │ │
│ │ - monitoring/ │ │
│ │ - projects/ │ │
│ │ - deploy-orchestrator/data/ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ SSH (pour DEV) │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ dev-portainer │ │ NGINX │ │
│ │ (192.168.1.51) │ │ (192.168.1.104) │ │
│ │ - docker DEV │ │ - reverse proxy │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Option A : Webhook (recommandé)
GitLab → POST http://prod-portainer.local:9500/webhook/gitlab → Daemon
Option B : Polling
Daemon → GET GitLab API /projects/{id}/events toutes les X minutes
Option C : Hybride
Webhook principal + Polling backup toutes les 15 min
| Endpoint | Méthode | Description |
|---|---|---|
/webhook/gitlab |
POST | Reçoit webhooks GitLab |
/api/deploys |
GET | Liste déploiements récents |
/api/deploys/{id} |
GET | Détails d'un déploiement |
/api/deploys/{id}/logs |
GET | Logs d'un déploiement |
/api/deploys/{id}/redeploy |
POST | Relance un déploiement |
/api/services |
GET | Liste services déployés |
/api/services/{name} |
GET | Détails d'un service |
/api/services/{name}/stop |
POST | Arrête un service |
/api/services/{name}/start |
POST | Démarre un service |
/api/crons |
GET | Liste tous les crons |
/api/crons/update |
POST | Met à jour status cron |
/api/dns/wildcards |
GET | Liste wildcards DNS |
/api/health |
GET | Health check daemon |
/mnt/stock_8to/33800-stack/projects/deploy-orchestrator/ # Sur prod-portainer
├── app/
│ ├── main.py # FastAPI app
│ ├── config.py # Configuration
│ ├── models.py # Modèles SQLAlchemy
│ │
│ ├── services/
│ │ ├── git.py # Clone/pull repos
│ │ ├── parser.py # Parse conf.gouroubleu.yml
│ │ ├── builder.py # Build Docker images
│ │ ├── deployer.py # Deploy containers
│ │ ├── nginx.py # Génère configs Nginx
│ │ ├── dns.py # Gère wildcards DNS
│ │ ├── storage.py # Gère volumes/stockage
│ │ ├── crons.py # Gère crons
│ │ ├── notifier.py # Notifications ntfy
│ │ └── registry.py # Met à jour registres JSON
│ │
│ ├── templates/
│ │ ├── Dockerfile.node
│ │ ├── Dockerfile.bun
│ │ ├── Dockerfile.python
│ │ ├── Dockerfile.rust
│ │ ├── Dockerfile.static
│ │ ├── nginx.conf.j2
│ │ └── docker-compose.yml.j2
│ │
│ └── routers/
│ ├── webhook.py
│ ├── deploys.py
│ ├── services.py
│ └── crons.py
│
├── data/
│ ├── deploys.db # SQLite état déploiements
│ └── repos/ # Repos clonés
│
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── conf.prod.gouroubleu.yml # Sa propre config !
1. WEBHOOK REÇU
└─ POST /webhook/gitlab
└─ Payload: {project, branch, commit, ...}
2. VALIDATION
└─ Branche supportée ? (main, develop, staging)
└─ Map vers env (main → prod, develop → dev)
3. CLONE/PULL
└─ git clone/pull dans /data/repos/{project}/
└─ Checkout branche
4. RECHERCHE CONFIG
└─ Cherche conf.{env}.gouroubleu.yml
└─ Si absent → Stop (pas de deploy auto)
5. PARSE CONFIG
└─ Valide YAML
└─ Vérifie champs obligatoires
└─ Résout services → variables env
6. STOCKAGE
└─ Crée dossiers si storage.data/uploads
└─ Configure volumes pour docker-compose
7. DNS
└─ Parse domaine demandé
└─ Vérifie/crée wildcard si nécessaire
8. BUILD
└─ Sélectionne template Dockerfile selon type
└─ docker build -t registry.../project:tag
└─ docker push
9. NGINX
└─ Génère config depuis template
└─ SCP vers nginx.local
└─ SSH nginx -t && reload
10. DEPLOY
└─ Génère docker-compose.yml
└─ SSH target: docker compose up -d
11. CRONS
└─ Génère /etc/cron.d/{name}-crons si crons définis
└─ Met à jour crons-registry.json
12. HEALTH CHECK
└─ Attend container UP
└─ Teste endpoint health si défini
13. REGISTRES
└─ Met à jour services-registry.json
└─ Met à jour crons-registry.json
└─ Met à jour dns-wildcards.json
14. NOTIFICATION
└─ POST ntfy: "✅ {name} déployé sur {env}"
└─ Ou "❌ {name} échec: {error}"
15. LOGS
└─ Stocke logs déploiement dans SQLite
└─ Accessible via API /api/deploys/{id}/logs
Le daemon détecte automatiquement le type de projet selon les fichiers présents :
| Fichier détecté | Type assigné | Framework détecté |
|---|---|---|
package.json + @builder.io/qwik |
qwik |
Qwik |
package.json + next |
next |
Next.js |
package.json + elysia ou hono |
bun |
Bun API |
package.json + express ou fastify |
node |
Node.js API |
package.json (autre) |
node |
Node.js générique |
bun.lockb (sans package.json type) |
bun |
Bun |
Cargo.toml |
rust |
Rust |
requirements.txt ou pyproject.toml |
python |
Python |
go.mod |
go |
Go |
index.html (sans package.json) |
static |
Site statique |
# ══════════════════════════════════════════════════════════════════════
# QWIK
# ══════════════════════════════════════════════════════════════════════
type: "qwik"
install: "bun install"
build: "bun run build" # génère dist/ ou server/
start: "bun run serve" # ou node server/entry.express.js
# ══════════════════════════════════════════════════════════════════════
# NEXT.JS
# ══════════════════════════════════════════════════════════════════════
type: "next"
install: "npm ci"
build: "npm run build" # génère .next/
start: "npm start"
# ══════════════════════════════════════════════════════════════════════
# NODE.JS (Express, Fastify, etc.)
# ══════════════════════════════════════════════════════════════════════
type: "node"
install: "npm ci --only=production"
build: "npm run build" # si script existe, sinon skip
start: "node dist/index.js"
# ══════════════════════════════════════════════════════════════════════
# BUN (Elysia, Hono, etc.)
# ══════════════════════════════════════════════════════════════════════
type: "bun"
install: "bun install --frozen-lockfile"
build: "bun run build" # si script existe
start: "bun run start"
# ══════════════════════════════════════════════════════════════════════
# PYTHON (FastAPI, Flask, etc.)
# ══════════════════════════════════════════════════════════════════════
type: "python"
install: "pip install -r requirements.txt"
build: null # pas de build
start: "uvicorn app.main:app --host 0.0.0.0 --port $PORT"
# ══════════════════════════════════════════════════════════════════════
# RUST
# ══════════════════════════════════════════════════════════════════════
type: "rust"
install: null
build: "cargo build --release"
start: "./target/release/app"
# ══════════════════════════════════════════════════════════════════════
# STATIC (sites statiques)
# ══════════════════════════════════════════════════════════════════════
type: "static"
install: "npm ci" # si package.json existe
build: "npm run build" # génère dist/
start: null # servi par nginx
Pour un contrôle complet du build :
name: "mon-projet-special"
target: "prod-portainer"
type: "custom"
# ══════════════════════════════════════════════════════════════════════
# OPTION 1 : Dockerfile custom (recommandé)
# ══════════════════════════════════════════════════════════════════════
dockerfile: "Dockerfile" # Utilise ton propre Dockerfile
# Le daemon fait juste: docker build + push + deploy
# ══════════════════════════════════════════════════════════════════════
# OPTION 2 : Commandes custom complètes
# ══════════════════════════════════════════════════════════════════════
build:
# Toutes les commandes sont custom
install: "pnpm install"
command: |
pnpm run generate
pnpm run build:server
pnpm run build:client
output: "build/"
start: "node build/server.js"
# Image de base custom
base_image: "node:20-bookworm" # Défaut: dépend du type
# ══════════════════════════════════════════════════════════════════════
# OPTION 3 : Script de build externe
# ══════════════════════════════════════════════════════════════════════
build:
script: "./scripts/build.sh" # Exécute un script custom
output: "dist/"
start: "./scripts/start.sh"
Tu peux aussi override juste certaines commandes sur un type standard :
name: "mon-projet-qwik"
target: "prod-portainer"
type: "qwik" # Type standard
# Override juste ce qui change
build:
command: "bun run build:prod" # Custom build, reste = défaut qwik
# Ou override le start
start: "node dist/server/entry.express.js"
conf.prod.gouroubleu.yml :
name: "mon-app-qwik"
target: "prod-portainer"
type: "qwik"
port: 3000
domain: "app"
supabase: true
redis: true
Dockerfile généré automatiquement :
# Auto-generated by deploy-orchestrator
# Type: qwik
FROM oven/bun:latest AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
FROM oven/bun:latest
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
CMD ["bun", "run", "serve"]
| Type | Runtime | Use case |
|---|---|---|
qwik |
Bun/Node | Apps Qwik (SSR) |
next |
Node | Apps Next.js |
node |
Node | APIs Express/Fastify |
bun |
Bun | APIs Elysia/Hono |
python |
Python | APIs FastAPI/Flask |
rust |
Native | APIs Actix/Axum |
go |
Native | APIs Go |
static |
Nginx | Sites statiques |
custom |
Custom | Dockerfile fourni |
# Dockerfile.node
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
EXPOSE ${PORT:-3000}
CMD ["node", "dist/index.js"]
# Dockerfile.bun
FROM oven/bun:latest AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM oven/bun:latest
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
EXPOSE ${PORT:-3000}
CMD ["bun", "run", "start"]
# Dockerfile.python
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV PYTHONUNBUFFERED=1
EXPOSE ${PORT:-5000}
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "${PORT:-5000}"]
# Dockerfile.rust
FROM rust:1.75-alpine AS builder
WORKDIR /app
RUN apk add --no-cache musl-dev
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN cargo build --release
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/target/release/app .
EXPOSE ${PORT:-8080}
CMD ["./app"]
# Dockerfile.static
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
| Fichier | Contenu | Utilisé par |
|---|---|---|
services-registry.json |
Liste services déployés | generate.sh |
crons-registry.json |
Liste crons avec status | generate.sh |
dns-wildcards.json |
Liste wildcards DNS | generate.sh |
deploys-history.json |
Historique déploiements | generate.sh |
| Projet | GitLab ID | Action |
|---|---|---|
| ai-orchestrator | 108 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
| notif-logger | 107 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
| browser-connector | 106 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
| connectors-api | 105 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
| needfinder | 92 | Créer conf.dev.gouroubleu.yml |
| ffmpeg-api | 90 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
| tintech-api | 83 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
| authentificator | 76 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
| ulias | 64 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
| medias-api | 60 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
| jellylink-api | 52 | Créer conf.prod.gouroubleu.yml, supprimer .gitlab-ci.yml |
⚠️ Exception : Le projet
deploy-orchestratorconserve son.gitlab-ci.ymlcar il ne peut pas s'auto-déployer (voir section 17).
Avant : .gitlab-ci.yml (50+ lignes)
Après : conf.prod.gouroubleu.yml
name: "ai-orchestrator"
type: "python"
port: 5501
domain: "ai-orchestrator"
supabase: true
redis: true
ntfy: true
storage:
data: true
volumes:
- source: "stock_8to/ai-data/outputs"
target: "/outputs"
logs: true
health: "/health"
websocket: true
env:
WIN11_HOST: "192.168.1.30"
WIN11_USER: "gouro"
secrets:
- WIN11_SSH_KEY
Le deploy-orchestrator ne peut PAS se déployer lui-même.
C'est le problème de la poule et l'œuf : s'il se redéploie et échoue à mi-chemin, il est cassé et ne peut plus se réparer.
Le deploy-orchestrator est le seul projet qui conserve son .gitlab-ci.yml :
| Projet | Méthode de déploiement |
|---|---|
| deploy-orchestrator | .gitlab-ci.yml (exception) |
| Tous les autres projets | conf.{branche}.gouroubleu.yml → deploy-orchestrator |
stages:
- build
- deploy
build:
stage: build
script:
- docker build -t deploy-orchestrator:latest .
- docker tag deploy-orchestrator:latest registry.33800.nowhere84.com/gouroubleu/deploy-orchestrator:latest
- docker push registry.33800.nowhere84.com/gouroubleu/deploy-orchestrator:latest
only:
- main
deploy:
stage: deploy
script:
- cd /mnt/stock_8to/33800-stack/projects/deploy-orchestrator
- docker compose pull
- docker compose up -d
only:
- main
| Risque | Impact | Mitigation |
|---|---|---|
| Webhook rate limit | Déploiements manqués | Queue Redis pour retry |
| DNS propagation lente | Domaine inaccessible | Cache + fallback IP directe |
| Build échoue | Service non déployé | Rollback automatique |
| O2switch API indisponible | Pas de nouveau wildcard | Cache wildcards existants |
| Secrets exposés | Sécurité | Vault centralisé + rotation |
Auteur : Claude Prochaine étape : Validation puis implémentation Phase 1