Portabase : centraliser les sauvegardes de toutes ses bases de données
Un dashboard, un agent par serveur, aucun port à ouvrir : installer Portabase pour planifier, chiffrer et restaurer ses dumps PostgreSQL, MySQL ou MongoDB depuis une seule interface.
Trois serveurs, cinq bases, et un pg_dump lancé par cron sur chacun. Ça marche jusqu'au jour où on se demande lequel tourne encore. Le script du VPS de staging a cassé après une mise à jour de Postgres. Personne ne l'a vu, puisque personne ne regarde les logs d'un cron qui « marche ».
Portabase est une plateforme open source (licence Apache 2.0) et auto-hébergée qui centralise les sauvegardes de bases de données. Un dashboard web sert de tour de contrôle. Sur chaque serveur qui héberge des bases, un petit agent écrit en Rust exécute les dumps, les chiffre et les envoie vers le stockage choisi. Le projet est jeune mais sort des versions à un rythme soutenu.
Un dashboard, des agents
L'architecture tient en deux composants :
┌──────────────────────────┐
│ Dashboard Portabase │
│ (Next.js + PostgreSQL) │
└────────────▲─────────────┘
│ ping sortant toutes les N secondes
┌───────────────────┼───────────────────┐
│ │ │
┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐
│ Agent VPS │ │ Agent NAS │ │ Agent prod │
│ Postgres │ │ MariaDB │ │ MongoDB │
└─────────────┘ └─────────────┘ └─────────────┘Le point important : le dashboard ne se connecte jamais aux bases. C'est l'agent qui contacte le dashboard à intervalle régulier. Il envoie son état et la liste de ses bases, et reçoit en retour les ordres : planning, sauvegarde manuelle, restauration.
Conséquence pratique, aucun port entrant à ouvrir sur les serveurs de bases de données. Un Postgres derrière un NAT, sur un réseau privé ou derrière un pare-feu strict reste joignable par son agent, et c'est tout ce qui compte. Pour un homelab à la maison, c'est ce détail qui m'a fait regarder l'outil de plus près.
Ce qu'il sait sauvegarder
| Moteur | Versions | Restauration |
|---|---|---|
| PostgreSQL | 12 à 18 | Oui |
| MySQL | 5.7, 8, 9 | Oui |
| MariaDB | 10, 11 | Oui |
| MongoDB | 4 à 8 | Oui |
| SQLite | 3.x | Oui |
| Firebird | 3.0 à 5.0 | Oui |
| SQL Server | 2017 à 2022, Azure SQL | Oui |
| Redis | 2.8+ | Non |
| Valkey | 7.2+ | Non |
| Volume Docker | Engine 20.10+ | Oui |
Pour Redis et Valkey, Portabase sauvegarde mais ne restaure pas depuis l'interface. Il faudra remettre le fichier RDB en place à la main.
Sous le capot, l'agent utilise les outils natifs de chaque moteur : pg_dump pour PostgreSQL, pg_dumpall en mode cluster quand on veut aussi les rôles et les droits. Rien d'exotique, donc. Un dump produit par Portabase se relit avec les commandes habituelles, celles qu'on voit dans les bases de PostgreSQL.
Installer le dashboard
Le dashboard a besoin de Docker et du plugin Compose. Deux chemins existent.
La voie rapide : le CLI
curl -sL https://portabase.io/install | bash
portabase dashboard create mon-dashboard --startLe CLI génère la configuration, crée le secret de chiffrement et démarre les conteneurs. L'interface répond sur http://localhost:8887. C'est la méthode recommandée par le projet, et la bonne pour tester en cinq minutes.
La voie maîtrisée : Docker Compose
Pour un serveur qu'on garde, je préfère un fichier Compose versionné. On sait ce qui tourne et on peut le relire dans six mois. Si la syntaxe vous est peu familière, le guide Docker Compose pose les bases.
mkdir portabase-dashboard && cd portabase-dashboardname: portabase-dashboard
services:
portabase:
container_name: portabase-app
image: portabase/portabase:latest
restart: always
env_file: .env
environment:
- TZ=Europe/Paris
ports:
- "8887:80"
volumes:
- portabase-data:/data
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
db:
container_name: portabase-pg
image: postgres:17-alpine
restart: always
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres-data:
portabase-data:Le fichier .env contient le secret qui chiffre les échanges avec les agents. On le génère, on ne l'invente pas :
openssl rand -hex 32PROJECT_URL=https://backup.mondomaine.fr
PROJECT_SECRET=colle_ici_la_sortie_de_openssl
POSTGRES_USER=portabase
POSTGRES_PASSWORD=un_vrai_mot_de_passe
POSTGRES_HOST=db
POSTGRES_PORT=5432
POSTGRES_DB=portabase
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}?schema=publicdocker compose up -d
docker compose logs -f portabasePROJECT_URL doit correspondre à l'adresse publique du dashboard, celle que les agents vont contacter. En production, on place le port 8887 derrière un reverse proxy avec HTTPS plutôt que de l'exposer tel quel.
Il existe aussi un docker run en une ligne avec une base interne embarquée dans le conteneur. La documentation le réserve aux tests, et je suis d'accord : la base interne de l'outil qui garde vos sauvegardes mérite un vrai Postgres.
Brancher un premier agent
Dans le dashboard, on crée une organisation puis un agent : Organisation > Settings > Agents > Add agent. Portabase génère une Edge Key, visible via le bouton Show Key. C'est la seule information dont l'agent a besoin.
Sur le serveur qui héberge les bases :
mkdir portabase-agent && cd portabase-agent
echo '{"databases": []}' > databases.json
docker network create portabase_networkLe fichier databases.json peut rester vide : depuis l'agent 1.19, on déclare les bases directement depuis le dashboard. Il faut quand même le créer avant le premier démarrage. Sinon Docker monte un dossier à sa place et l'agent ne démarre pas.
name: portabase-agent
services:
app:
container_name: portabase-agent
image: portabase/agent:latest
restart: always
volumes:
- ./databases.json:/config/config.json
extra_hosts:
- "localhost:host-gateway"
environment:
TZ: "Europe/Paris"
POLLING: 5
APP_ENV: production
LOG: info
EDGE_KEY: "${EDGE_KEY}"
networks:
- portabase
networks:
portabase:
name: portabase_network
external: trueEDGE_KEY=la_cle_copiee_depuis_le_dashboarddocker compose up -dQuelques secondes plus tard, la colonne Last Contact de l'agent affiche une date récente et le statut passe au vert. L'onglet Health montre l'historique des pings sur douze heures : une grille de cases vertes, et rouges quand l'agent a manqué un rendez-vous.
Comment l'agent voit vos bases
C'est la partie où on se trompe le plus souvent. Deux cas :
- La base tourne dans un conteneur. On l'ajoute au réseau
portabase_network. Dans la configuration, l'hôte est le nom du service (postgres), paslocalhost. - La base est installée directement sur l'hôte. La ligne
extra_hostsfait pointerlocalhostvers la machine hôte. On renseigne alorslocalhostet le port habituel.
Pour une stack existante, ça se résume à trois lignes dans son Compose :
services:
postgres:
image: postgres:17-alpine
networks:
- default
- portabase
networks:
portabase:
name: portabase_network
external: truePlanifier, conserver, alerter
Une fois la base ajoutée (Agent > Add database, formulaire ou JSON), tout se règle depuis sa page de détail.
Le planning est une expression cron classique. 0 2 * * * pour tous les jours à 2 h, 0 */6 * * * toutes les six heures. Le mode Manual désactive l'automatique sans supprimer le bouton Backup now.
La rétention propose trois stratégies :
| Stratégie | Principe | Pour quoi |
|---|---|---|
count | Garde les N dernières sauvegardes (7 par défaut) | Bases de dev, disque limité |
days | Garde ce qui a moins de N jours | Contrainte de durée simple |
gfs | Grand-père / père / fils : quotidien, hebdo, mensuel | Production |
Attention à un piège : supprimer le planning supprime aussi la politique de rétention. Il faudra la recréer si on remet un planning.
Les alertes se branchent sur des canaux : email, Slack, Discord, Telegram, Gotify, Healthchecks, webhook générique… et ntfy. Si vous avez déjà ntfy pour les notifications push, c'est trois champs à remplir : URL du serveur, topic, token éventuel. Les événements disponibles couvrent l'échec et la réussite d'une sauvegarde ou d'une restauration, et surtout error_health_database, déclenché quand l'agent n'arrive plus à joindre la base.
Mon réglage : alerte sur tous les échecs vers ntfy, rien sur les succès. Une notification quotidienne « tout va bien » finit toujours par être ignorée.
Où vont les fichiers
Par défaut, les sauvegardes restent sur le disque du dashboard. C'est mieux que rien, mais si le serveur du dashboard meurt, les sauvegardes partent avec.
Les canaux de stockage disponibles : local, S3 (AWS, MinIO, Scaleway, Backblaze…), SFTP, Google Drive, Azure Blob, Google Cloud Storage, et rclone pour tout le reste. On peut attacher plusieurs politiques de stockage à une même base : le fichier part alors vers toutes les destinations. Pour chaque sauvegarde, l'onglet Backups affiche le statut d'envoi par canal, avec la vérification de taille et de somme de contrôle.
Une base, deux destinations, un hors site : la règle 3-2-1 en trois clics.
Le chiffrement se fait en AES-GCM. Les fichiers arrivent sur le stockage avec l'extension .enc. La clé maître se télécharge dans Settings > Storage, et c'est le moment de la ranger dans un gestionnaire de mots de passe. Elle permet de déchiffrer une sauvegarde sans le dashboard, avec le CLI :
portabase decrypt backup.tar.gz.enc backup.tar.gz --key master_key.bin
# ou tout un dossier
portabase decrypt ./backups ./restored --key master_key.binJe considère cette commande comme la fonctionnalité la plus importante de l'outil. Le jour où le dashboard a disparu, c'est elle qui sépare une sauvegarde utile d'un tas de fichiers illisibles.
Restaurer
L'onglet Restore propose deux sources : une sauvegarde de la liste, ou un fichier présent sur un des stockages. On peut aussi importer un dump externe depuis l'onglet Backups.
La restauration écrase la base cible. Pour PostgreSQL, l'option clean_mode règle la façon dont la cible est vidée avant :
| Valeur | Comportement |
|---|---|
none | Aucun nettoyage : pour une base cible vide |
clean | pg_restore --clean --if-exists, le défaut |
drop_schemas | Supprime tous les schémas non système avant de restaurer |
drop_database | Supprime et recrée la base entière |
Le défaut clean ne supprime que les objets présents dans le dump. Une table créée depuis la sauvegarde survit et peut faire échouer la restauration. Pour un vrai retour à zéro, drop_schemas est le bon choix, y compris sur un Postgres managé où l'on n'a pas le droit de supprimer la base.
Mon conseil : restaurez d'abord vers une base de test. Ajoutez une seconde entrée pointant vers un Postgres jetable, restaurez dessus, vérifiez les données. Ça prend dix minutes et ça répond à la seule question qui compte.
Portabase ou un script maison
Script pg_dump + Restic | Portabase | pgBackRest | |
|---|---|---|---|
| Interface web | Non | Oui | Non |
| Plusieurs moteurs | À scripter un par un | 9 moteurs + volumes Docker | PostgreSQL seul |
| Restauration point-in-time | Non | Non | Oui (WAL) |
| Alertes | À ajouter soi-même | Intégrées | À ajouter soi-même |
| Équipe, SSO | Non | Organisations, OIDC | Non |
| Composants à maintenir | Un script | Dashboard + agents | Un outil |
Si vous avez un seul serveur et une seule base, un script et Restic pour la sauvegarde 3-2-1 du serveur restent plus simples. Portabase ajoute un dashboard, une base interne et des agents à maintenir : c'est un coût réel.
Il devient rentable dès qu'on a plusieurs serveurs ou plusieurs moteurs, ou dès que quelqu'un d'autre que vous doit pouvoir lancer une restauration. Il ne remplace pas pgBackRest ou WAL-G pour une base critique qui demande une restauration à la seconde près : Portabase fait des dumps logiques, pas de l'archivage continu des WAL.
Les deux approches se combinent bien. Portabase gère les bases, Restic gère les fichiers de configuration et les volumes applicatifs.
Une sauvegarde de base de données ne vaut que par sa dernière restauration réussie. Portabase n'invente rien sur ce point : il s'appuie sur les outils de dump natifs de chaque moteur, comme un script. Ce qu'il change, c'est la visibilité. Chaque base a un statut, chaque échec envoie une alerte, et chaque restauration se lance en deux clics au lieu d'une procédure à retrouver dans un vieux README. Pour un homelab qui grandit, c'est exactement ce qui manquait entre le cron oublié et l'outil d'entreprise.