# Backup & Recovery OpenPanel (Dokploy + Cloudflare R2)

Stratégie minimale : **on ne sauvegarde pas les conteneurs** (recréés depuis le
compose, déjà versionné dans Dokploy + ce repo) — **on sauvegarde la donnée
persistante = les volumes Docker nommés**, et uniquement l'essentiel.

## Quoi sauvegarder

| Volume | Contenu | Verdict |
|---|---|---|
| `op-db-data` (PostgreSQL) | comptes, orgs, projets, dashboards, client IDs, settings | 🟢 **ESSENTIEL** (minuscule, irremplaçable) |
| `op-ch-data` (ClickHouse) | tous les events analytics (historique) | 🟡 optionnel (volumineux ; perte = perte d'historique seulement) |
| `op-kv-data` (Redis) | queue BullMQ + cache | 🔴 ignorer (éphémère) |
| `op-ch-logs` | logs ClickHouse | 🔴 ignorer |

**Minimum vital = `op-db-data`.** Ajouter `op-ch-data` seulement si l'historique compte.

> Les bind mounts `../files/clickhouse_config|users|init` ne sont **pas**
> sauvegardables en Volume Backup (bind mounts exclus) — mais ce n'est que de la
> config, déjà dans ce repo. Rien d'essentiel n'est un bind mount.

## Mécanisme : Volume Backups (PAS Database Backups)

- **Database Backups** (natif) = `pg_dump`/`mysqldump`/`mongodump` → PostgreSQL,
  MySQL, MariaDB, MongoDB **uniquement** (pas ClickHouse), et orienté *Database
  services* managés, pas un stack Compose.
- **Volume Backups** = volumes nommés → S3, **compatible Compose**. C'est la voie.
  ⚠️ Volumes nommés seulement (pas les bind mounts).

## 1. Destination S3 = Cloudflare R2

Cloudflare → R2 → créer un bucket (ex. `dokploy-backups`) + un **API token**
"Object Read & Write" scopé à ce bucket (copier la Secret Key, non récupérable).

Dokploy → `/dashboard/settings/destinations` → New (S3 Compatible) :

| Champ | Valeur |
|---|---|
| Name | `Cloudflare R2` |
| Endpoint | `https://<account-id>.r2.cloudflarestorage.com` (**sans** le bucket) |
| Region | `WNAM` / `ENAM` / `WEUR` / `APAC` (celle du bucket — **pas** `auto`) |
| Bucket | `dokploy-backups` |
| Access Key / Secret Key | du token R2 |
| **Force Path Style** | ✅ **OBLIGATOIRE pour R2** |

Tester la connexion avant de continuer.

## 2. Volume Backup — PostgreSQL (essentiel)

Dokploy → app OpenPanel → **Volume Backups** → Create :

- Name : `op-db-daily`
- Service Name : `op-db` (autocomplete) → Volume : `op-db-data`
- Schedule (cron) : `0 3 * * *` (tous les jours 3h)
- Destination : `Cloudflare R2`
- Prefix : `openpanel/postgres/`
- **Turn off Container : ✅ activé** (stoppe op-db qq secondes → cohérence ;
  op-api/op-worker se reconnectent ensuite, `restart: always`)

## 3. (Optionnel) Volume Backup — ClickHouse (historique)

Idem avec `op-ch` / `op-ch-data`, prefix `openpanel/clickhouse/`, cron hebdo
(`0 4 * * 0`), **Turn off Container activé**. Ne le fais que si l'historique
analytics compte (volumineux).

> Alternative plus propre pour CH (sans coupure, incrémental) : la commande native
> `BACKUP DATABASE openpanel TO S3('https://<acct>.r2.cloudflarestorage.com/bucket/path','<key>','<secret>')`
> via une Dokploy Schedule. Plus avancé — à considérer si le volume CH grossit.

## Restore

- **Volume Backup** : Dokploy → Volume Backups → restore depuis l'entrée S3
  (recrée le volume à partir de l'archive). Restaurer **la même version d'engine**
  (PostgreSQL 14 / ClickHouse 25.10) — les copies de volume brut ne sont pas
  portables entre versions majeures.
- Après restore : `docker compose up -d` (ou redeploy Dokploy) → les conteneurs
  se recréent sur les volumes restaurés.

## Ce qu'on NE sauvegarde pas (et pourquoi)

- **Conteneurs / images** : stateless, recréés depuis le compose.
- **`op-kv-data`** : queue/cache éphémères (au pire qq events in-flight perdus).
- **bind mounts `files/`** : config, déjà dans ce repo.

## Checklist minimale

- [ ] R2 bucket + API token (Object R/W, scopé)
- [ ] Destination R2 dans Dokploy (**Force Path Style ✅**)
- [ ] Volume Backup `op-db-data` quotidien, container-stop ✅
- [ ] (option) Volume Backup `op-ch-data` hebdo
- [ ] Tester un restore une fois (le backup non testé n'existe pas)
