dbtote
A cross-platform CLI tool for backing up and restoring databases, with compression, encryption at rest, and pluggable storage backends.
Full design: docs/SPEC.md.
Status
Phase 3: MySQL and PostgreSQL full backup/restore, local storage, gzip compression, age encryption, OS-keyring credentials, YAML config file with targets, test-connection, list, and CI/CD (gated releases, Docker image, keyless artifact signing, PR coverage comments).
Install
No tagged releases yet — versioned binaries land in Phase 3 once goreleaser is wired up. Until then, build from source:
git clone https://github.com/nweber23/dbtote.git
cd dbtote
go build -o bin/dbtote ./cmd/dbtote
Requires the mysqldump/mysql client binaries on PATH for MySQL targets, and pg_dump/pg_restore for PostgreSQL targets.
Quick start
-
Store the database password once:
dbtote secret set --target prod-mysql
-
Run a backup:
dbtote backup --target prod-mysql --host db.internal --user backup_svc --database app_production --output /var/backups/dbtote
-
Encrypt backups at rest with age:
age-keygen -o identity.txt # prints the matching public key
dbtote backup --target prod-mysql --host db.internal --user backup_svc --database app_production \
--output /var/backups/dbtote --encrypt --recipient age1...
-
Restore:
dbtote restore --target prod-mysql --from /var/backups/dbtote/prod-mysql_full_....sql.gz.age \
--host db.internal --user backup_svc --database app_production --decrypt-key identity.txt --yes
Config file
Instead of passing --host/--user/--database/etc. on every command, define targets once in a YAML config file. CLI flags always override the config for that run.
-
Write a starter config:
dbtote config init
Prompts for a local storage path and writes $XDG_CONFIG_HOME/dbtote/config.yaml (or --config <path>).
-
Add a target under targets:, e.g.:
targets:
prod-mysql:
engine: mysql
host: db1.internal
port: 3306
user: backup_svc
database: app_production
storage: local-main
-
Validate the config — checks storage references, age recipient keys, cron expressions, and that every target's credential resolves (password_env or OS keyring), without connecting to any database:
dbtote config validate
-
Check connectivity and credentials for a specific target:
dbtote test-connection --target prod-mysql
-
List known backups:
dbtote list
dbtote list --target prod-mysql --since 24h
dbtote list --json
With a config file in place, dbtote backup --target prod-mysql and dbtote restore --target prod-mysql --from <file> --yes resolve engine, host, port, user, database, storage, and encryption recipients from the target's config entry — pass any of --host/--user/--database/etc. explicitly to override just that field for one run.
PostgreSQL
Postgres is supported the same way MySQL is — set engine: postgres on a target. Requires the pg_dump and pg_restore client binaries on PATH.
Releasing
Pushing a v* tag triggers .github/workflows/release.yml: it gates on the full CI suite, then runs goreleaser to cross-compile binaries for linux/darwin/windows (amd64 + arm64, minus windows/arm64), checksum them, and publish a GitHub Release with an auto-generated changelog. Every release artifact is signed keylessly with cosign (Sigstore's OIDC-based flow — no signing key to manage), and a multi-arch Docker image is published to ghcr.io/nweber23/dbtote:
docker run --rm ghcr.io/nweber23/dbtote:latest backup --target prod-mysql --host db.internal --user backup_svc --database app_production --output /backups
Homebrew isn't set up yet — it needs a separate nweber23/homebrew-dbtote tap repository and a HOMEBREW_TAP_GITHUB_TOKEN secret, neither of which exist yet (link here once they do).
Development
make build # compile ./cmd/dbtote
make test # unit tests
make test-integration # requires Docker; spins up real MySQL via testcontainers
make lint # golangci-lint