Self-hostable, scheduled backups for MySQL, MariaDB, PostgreSQL and MongoDB — with a web UI, a REST API, and encryption at rest.
Backup Hub is a small, self-hostable service that takes scheduled, encrypted backups of your MySQL, MariaDB, PostgreSQL and MongoDB databases, and gives you a web UI and a REST API to manage them. Hosted databases work too — Amazon RDS / Aurora, PlanetScale and TiDB with the MySQL engine; Supabase, Neon and TimescaleDB with the PostgreSQL engine; MongoDB Atlas with the MongoDB engine (see supported databases).
Point it at a database, give it a cron schedule and a retention count, and it handles the rest: running mysqldump/pg_dump on schedule, compressing and optionally encrypting the result, pruning old backups, copying them off-site, notifying you if a run fails, and letting you download — or restore — any backup from the browser.
A few highlights (the full list, with all the detail, is in code/README.md):
- A dashboard that shows at a glance what succeeded, what failed and what needs attention — including backups that couldn't sign in to their database or save location
- Multi-user accounts with two roles (
admin/user), TOTP + passkey two-factor auth, and per-task sharing - Sign-in security: a history of every sign-in attempt, lockouts after repeated failures, "forgot password" reset links by email, and a list of your active sessions you can sign out of
- One-click restore, and a dry run button to test a backup before you save the task
- Backup checks: every backup is read back and fingerprinted automatically, and can be test-restored into a throwaway database on a scratch server, so you know it actually restores
- Off-site copies to S3-compatible storage (AWS, Backblaze B2, Wasabi, R2, MinIO…), Google Drive, OneDrive, OpenCloud, FTP/FTPS, SFTP or email — set a location up once and reuse it across tasks
- Notifications by email, ntfy, Telegram or Discord, per task and system-wide
- A scoped REST API (
tasks:read,tasks:write,runs:trigger,runs:download,runs:delete) for driving Backup Hub from other tools - Everything encrypted at rest — stored DB credentials, save-location and notification credentials, backup passwords, and (optionally) the backup files themselves
- Works on your phone as well as your desktop
- Available in English, Spanish, German, French and Simplified Chinese — it follows your browser's language, or pick one on your Account page (see languages)
The dashboard — the last 7 days at a glance, what needs attention (like a database password that changed, or an expired cloud key), recent activity, and every task with its latest result and a strip of its recent backups.
Every backup's actions in one place — download, restore, verify the file, test-restore it, or delete it. Check results show under each backup's status.
Save locations — enter credentials once and reuse them on any task, or keep them task-only.
|
Notifications — email, ntfy, Telegram or Discord, chosen per task.
|
On your phone — a compact dashboard with your tasks up front, a jump-to menu for a task's sections, and every backup's actions in a bottom drawer.
More screenshots — task details, new task, account and security, users, API keys and sign-in
API keys — scoped keys for the REST API, optionally pinned to one task.
|
Sign in — with a password (plus two-factor) or a passkey, with a "Forgot password?" link.
|
Screenshots show demo data, generated with the scripts in code/scripts/screenshots/.
This repo is split into these top-level folders:
| Folder | What's in it |
|---|---|
code/ |
The actual application — a Next.js app. This is where you'll find the source, the full project documentation (code/README.md), and the license. |
docs/ |
Screenshots used in this README. |
docker/ |
Everything needed to run Backup Hub in Docker: the Dockerfile, a ready-to-use docker-compose.yml (Backup Hub + a MySQL container), the container's entrypoint.sh, and a multi-arch build.sh build script. |
If you're just here to run it, you don't need to open code/ at all — start with docker/ below.
The docker/ folder ships a docker-compose.yml that runs Backup Hub alongside its own MySQL database (the one Backup Hub uses to store its users, tasks, and run history — separate from whatever databases you point it at to back up).
cd docker
cp example.env-file .envEdit .env and set real values — at minimum, generate a real secret for BACKUP_MASTER_KEY (used to encrypt stored DB credentials and backup passwords at rest) and set strong MySQL passwords:
openssl rand -hex 32 # → BACKUP_MASTER_KEYThen bring it up:
docker compose up -dVisit http://localhost:3000 — there are no users yet, so you'll land on /setup to create the first admin account.
Worth setting in .env before you rely on it day to day:
BH_APP_URL— the address people open Backup Hub at (e.g.https://backup.yourdomain.com). Password-reset emails link to it.BH_SMTP_*— needed for email notifications and for "Forgot password?". Without SMTP, users who forget their password have to ask an admin.BH_LOGIN_MAX_FAILED_ATTEMPTS(default2),BH_LOGIN_MAX_FAILED_ATTEMPTS_PER_IP(default20) andBH_LOGIN_LOCKOUT_MINUTES(default15) — how many failed sign-ins lock an account or an IP address, and for how long.0turns a limit off.- If Backup Hub sits behind a reverse proxy, have it set
X-Forwarded-For; sign-in history, active sessions and the per-IP lockout use it to see the visitor's IP.
Upgrading: pull the new image and docker compose up -d again. Database migrations run automatically on start.
For reference, here's the shape of it — Backup Hub itself, plus a MySQL container it depends on and waits for to be healthy before starting:
services:
backup-hub:
image: myfin/backup-hub:latest
container_name: backup-hub
restart: always
ports:
- "3000:3000"
environment:
APP_DATABASE_URL: "mysql://${BH_DATABASE_USER}:${BH_DATABASE_PASSWORD}@${BH_DATABASE_HOST}:3306/${BH_DATABASE_NAME}"
# Read by entrypoint.sh
BH_DATABASE_HOST: "${BH_DATABASE_HOST}"
BH_DATABASE_NAME: "${BH_DATABASE_NAME}"
BH_DATABASE_USER: "${BH_DATABASE_USER}"
BH_DATABASE_PASSWORD: "${BH_DATABASE_PASSWORD}"
BH_DATABASE_ROOT_PASSWORD: "${BH_DATABASE_ROOT_PASSWORD}"
BACKUP_MASTER_KEY: "${BH_BACKUP_MASTER_KEY}"
BACKUP_DIR: "/var/backups/backup-hub"
SMTP_HOST: "${BH_SMTP_HOST}"
SMTP_PORT: "${BH_SMTP_PORT}"
SMTP_SECURE: "${BH_SMTP_SECURE}"
SMTP_USER: "${BH_SMTP_USER}"
SMTP_PASS: "${BH_SMTP_PASS}"
SMTP_FROM: "${BH_SMTP_FROM}"
WEBAUTHN_RP_NAME: "${BH_WEBAUTHN_RP_NAME}"
WEBAUTHN_RP_ID: "${BH_WEBAUTHN_RP_ID}"
WEBAUTHN_ORIGIN: "${BH_WEBAUTHN_ORIGIN}"
APP_URL: "${BH_APP_URL:-${BH_WEBAUTHN_ORIGIN}}"
# Optional: sign-in lockouts (see example.env-file).
LOGIN_MAX_FAILED_ATTEMPTS: "${BH_LOGIN_MAX_FAILED_ATTEMPTS:-2}"
LOGIN_MAX_FAILED_ATTEMPTS_PER_IP: "${BH_LOGIN_MAX_FAILED_ATTEMPTS_PER_IP:-20}"
LOGIN_LOCKOUT_MINUTES: "${BH_LOGIN_LOCKOUT_MINUTES:-15}"
# Optional: servers for test restores (see example.env-file).
VERIFY_MYSQL_URL: "${BH_VERIFY_MYSQL_URL:-}"
VERIFY_POSTGRES_URL: "${BH_VERIFY_POSTGRES_URL:-}"
VERIFY_MONGODB_URL: "${BH_VERIFY_MONGODB_URL:-}"
volumes:
- ./data/backups:/var/backups/backup-hub
depends_on:
backup-hub-db:
condition: service_healthy
networks:
- backup-hub-frontend
- backup-hub-backend
backup-hub-db:
image: mysql:latest
container_name: backup-hub-db
restart: always
environment:
MYSQL_DATABASE: "${BH_DATABASE_NAME}"
MYSQL_USER: "${BH_DATABASE_USER}"
MYSQL_PASSWORD: "${BH_DATABASE_PASSWORD}"
MYSQL_ROOT_PASSWORD: "${BH_DATABASE_ROOT_PASSWORD}"
volumes:
- ./data/mysql:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${BH_DATABASE_ROOT_PASSWORD}"]
interval: 5s
timeout: 5s
retries: 10
networks:
- backup-hub-backend
networks:
backup-hub-frontend:
driver: bridge
backup-hub-backend:
driver: bridge
internal: trueA few things worth knowing about this setup:
./data/backupsand./data/mysqlare bind-mounted intodocker/data/on the host, so both your backup files and Backup Hub's own database survive adocker compose down/up.entrypoint.sh(baked into the image) waits for MySQL to report healthy, creates the database/user if they don't exist yet, runs any pending migrations, then starts the app — sodocker compose up -dalone is enough on a first run.- The image already includes
mariadb-dump(which backs up MySQL and MariaDB),pg_dump/pg_isready, andmongodump/mongorestore, so it can back up every engine out of the box — you don't need to install anything extra to back up a Postgres or MongoDB database even though the bundleddbservice here is MySQL (that MySQL container is only for Backup Hub's own data).
If you'd rather build locally instead of pulling myfin/backup-hub:latest, use the provided script from the repo root:
./docker/build.sh 1.0.0 myfin/backup-hub # builds locally
./docker/build.sh 1.0.0 myfin/backup-hub --push # builds & pushes (multi-arch)It builds from docker/dockerfile with the repo root as build context, so run it from the repo root as shown above. Each build gets its own deployment ID (version + build number), so after an upgrade, browser tabs still open on the old version reload themselves instead of erroring.
You can also run Backup Hub directly with Node — see code/README.md for that path (you'll need mysqldump and/or pg_dump installed locally in that case, since there's no image doing it for you).
Everything else — the full feature list, user roles and permissions, the REST API reference, two-factor auth and passkeys, sign-in security (history, lockouts, password reset, active sessions), how encrypted backups work, and a troubleshooting guide for common mysqldump/pg_dump version-mismatch errors — lives in code/README.md.
MIT — see code/LICENSE.













