Help us shape the future of Go! Take the 2026 Go Developer Survey

dbtote

module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: MIT

README

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 5: MySQL, PostgreSQL, SQLite, and MongoDB full backup/restore, local and S3 storage, gzip compression, age encryption, OS-keyring credentials, YAML config file with targets, test-connection, list (backed by a local SQLite metadata index), retention policy enforcement, Slack success/failure notifications, and CI/CD (gated releases, Docker image, keyless artifact signing, PR coverage comments).

Install

Download a prebuilt binary from the latest release, or pull the Docker image:

docker pull ghcr.io/nweber23/dbtote:latest

Or install with Go (builds from source, so dbtote --version won't show a real version — use a prebuilt binary or the Docker image above if you need that):

go install github.com/nweber23/dbtote/cmd/dbtote@latest

Or build from a local clone:

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, pg_dump/pg_restore for PostgreSQL targets, and mongodump/mongorestore (from MongoDB Database Tools) for MongoDB targets. SQLite needs no external binary — it's backed up via SQLite's own VACUUM INTO, run through the pure-Go modernc.org/sqlite driver.

Quick start

  1. Store the database password once:

    dbtote secret set --target prod-mysql
    
  2. Run a backup:

    dbtote backup --target prod-mysql --host db.internal --user backup_svc --database app_production --output /var/backups/dbtote
    
  3. 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...
    
  4. 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.

  1. Write a starter config:

    dbtote config init
    

    Prompts for a local storage path and writes $XDG_CONFIG_HOME/dbtote/config.yaml (or --config <path>).

  2. 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
    
  3. 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
    
  4. Check connectivity and credentials for a specific target:

    dbtote test-connection --target prod-mysql
    
  5. 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.

SQLite and MongoDB

SQLite targets use path: instead of host/port/user/database, and need no credential — there's no password_env or keyring entry to set up:

targets:
  local-sqlite:
    engine: sqlite
    path: /var/backups/app.db
    storage: local-main

MongoDB targets use uri_env: — the name of an environment variable holding the full connection URI (including auth), never the URI itself in the config file:

targets:
  prod-mongo:
    engine: mongodb
    uri_env: MONGO_URI
    database: app_production
    storage: s3-main
dbtote backup --target local-sqlite
MONGO_URI=mongodb://user:pass@mongo.internal:27017 dbtote backup --target prod-mongo

S3 storage, retention, and notifications

Add an S3 (or S3-compatible) backend under storage: — credentials resolve via the standard AWS chain (env vars, shared config, instance/workload identity), never a dbtote-specific setting:

storage:
  s3-main:
    type: s3
    bucket: prod-dbtote-backups
    region: us-east-1
    prefix: dbtote/

Set a retention policy under defaults: (applies to every target) and/or per-target — keep_last and keep_days are a union, a backup survives if it satisfies either:

defaults:
  retention:
    keep_last: 7
    keep_days: 30

See what a policy would delete without deleting anything, then enforce it:

dbtote retention preview --target prod-mysql
dbtote retention apply --target prod-mysql

Get a Slack message on backup success and/or failure — webhook_url_env names an env var holding the webhook URL, never the URL itself in the config file:

notify:
  slack:
    webhook_url_env: SLACK_WEBHOOK_URL
    on: [success, failure]

dbtote backup sends notifications by default when notify.slack is configured; pass --notify=false to skip one run without touching the config.

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL