Skip to content

Repository files navigation

database/backup

Lightweight Docker image based on Debian Slim to automate PostgreSQL, MariaDB/MySQL, and MongoDB database backups with a built-in Go scheduler.

Features

  • PostgreSQL (any client version available in the PGDG repository; default 18), MariaDB/MySQL, and MongoDB support
  • Dynamic client installation at startup (only required tools are installed based on config)
  • Lightweight image (~126 MB): no database clients pre-installed
  • YAML configuration (backups.yml) with full validation at startup
  • In-process scheduler — no external cron daemon
  • Streaming gzip compression for all dump formats
  • Optional at-rest encryption: GPG (symmetric OpenPGP) or age (passphrase or public-key recipients)
  • Optional TLS for database connections
  • Credentials never exposed on process command lines
  • Non-root runtime: container drops to uid 1000 after installing clients
  • Built-in healthcheck using native database drivers (no mongosh download)
  • Migration command to convert v1 backups.conf to YAML
  • Centralized logging to container stdout
  • Automated build via GitHub Actions
  • Multi-architecture images (amd64, arm64)
  • Automatic rebuild when the base image (debian:trixie-slim) is updated (4 times daily)

Quick Start

Using the pre-built image from GitHub Container Registry

docker pull ghcr.io/greite/database-backup:latest

Example compose.yml using the pre-built image:

services:
db-backup:
image: ghcr.io/greite/database-backup:latestcontainer_name: db-backuprestart: unless-stoppedvolumes:
- ./backups:/backups
- ./backups.yml:/config/backups.yml:ronetworks:
- db-network

Available Tags

  • latest — Latest stable version
  • v2.0.0 — Tagged release version (semantic versioning: v2.0.0, 2.0, 2)

Unraid (Community Applications)

An Unraid template for this image is maintained in the Greite/unraid-templates repository and is published on the Community Applications store.

Install via Community Applications:

  1. Open the Apps tab in your Unraid web UI
  2. Search for database-backup
  3. Click Install and adjust the paths:
    • Backups Storage — host path that will receive the dumps (default /mnt/user/appdata/database-backup/backups)
    • Configuration File — host path to your backups.yml (default /mnt/user/appdata/database-backup/backups.yml); copy backups.yml.example there first and edit it
    • Timezone — IANA timezone name (default Europe/Paris)
  4. Click Apply

Manual install (without Community Applications):

In the Unraid web UI go to Docker → Add Container, set the Template URL to:

https://raw.githubusercontent.com/Greite/unraid-templates/main/database-backup/database-backup.xml

Then load the template and follow the same steps as above.

Project Structure

.
├── Dockerfile
├── compose.yml # Example with test databases
├── backups.yml.example # Configuration template
├── docs/
│ └── MIGRATION-V2.md # Migration guide from v1
├── .github/
│ └── workflows/
│ ├── docker-build.yml # Build workflow on version tags
│ ├── go-ci.yml # Go lint, unit, and integration tests
│ └── base-image-check.yml # Scheduled rebuild on base image update
├── cmd/
│ └── dbbackup/ # Main binary
└── internal/ # Internal packages (config, scheduler, crypto…)

Configuration

Config File (backups.yml)

Mount the file at /config/backups.yml inside the container. Use chmod 600 on the host because the file contains credentials (or use password_file everywhere).

See backups.yml.example for a ready-to-use template.

Schema

Top-level fields:

FieldRequiredDefaultDescription
defaults.retention_daysNo7Retention days applied to all jobs unless overridden
defaults.tlsNofalseTLS flag applied to all jobs unless overridden
shutdown_graceNo5mGrace period for in-flight backups on container stop. Must be a duration string ("5m", "300s", "1h") — bare integers are rejected
encryptionNoAt-rest encryption block (see below)
jobsYesList of backup jobs

Per-job fields:

FieldRequiredDefaultDescription
nameYesUnique job identifier (used in log output and directory paths)
typeYespostgres, mariadb, mysql, or mongodb
hostYesDatabase hostname or IP
portNo5432 / 3306 / 27017Connection port
databaseYesDatabase name to back up
userNo*Database user
passwordNo*Inline password (any character, including |)
password_fileNo*Path to a file containing the password (e.g. a Docker secret)
scheduleYesCron expression ("0 2 * * *") or shorthand ("@daily")
retention_daysNofrom defaults or 7Number of days to keep backups
pg_versionNo18PostgreSQL client version (any version available in the PGDG repository, default 18); PostgreSQL only
tlsNofrom defaults or falsetrue to encrypt the database connection

*user and password / password_file are required for PostgreSQL and MariaDB. For MongoDB they are optional (omit for unauthenticated dev/test setups).

Encryption block

GPG (symmetric OpenPGP) — decrypt with gpg -d file.gpg:

encryption:
method: gpgpassphrase_file: /run/secrets/backup_passphrase# or: passphrase: my_passphrase (visible in docker inspect — avoid in production)

age (passphrase) — decrypt with age -d:

encryption:
method: agepassphrase_file: /run/secrets/backup_passphrase

age (public-key recipients) — the container never holds the private key:

encryption:
method: agerecipients:
- age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

The v1 environment variables BACKUP_ENCRYPTION_PASSPHRASE and BACKUP_ENCRYPTION_PASSPHRASE_FILE remain supported and produce the same GPG-compatible files when no encryption: block is present.

Docker secrets (password_file)

services:
db-backup:
image: ghcr.io/greite/database-backup:latestsecrets:
- db_password# In backups.yml: password_file: /run/secrets/db_passwordsecrets:
db_password:
file: ./secrets/db_password.txt

Usage

Volumes

Mount pointPurpose
/config/backups.ymlYAML configuration file (required)
/backupsDirectory where backup files are written

Subcommands

The container binary (dbbackup) supports the following subcommands. When no subcommand is given, run is the default.

SubcommandDescription
runLoad config and start the built-in scheduler (default)
validateParse and validate the config, then exit; useful in CI
backup --job <name>Run a single named job immediately (use with docker exec)
healthcheckCheck connectivity for all configured databases and exit
migrate [path]Convert a v1 pipe-delimited file to YAML and print to stdout

Docker Compose (recommended)

  1. Copy and edit the example config:
cp backups.yml.example backups.yml
chmod 600 backups.yml
# Edit backups.yml with your settings
  1. Start the services:
docker compose up -d
  1. Check the logs:
docker compose logs -f db-backup

Docker Run

docker run -d \
--name db-backup \
-v $(pwd)/backups:/backups \
-v $(pwd)/backups.yml:/config/backups.yml:ro \
ghcr.io/greite/database-backup:latest

Validate before deploying

docker run --rm \
-v $(pwd)/backups.yml:/config/backups.yml:ro \
ghcr.io/greite/database-backup:latest validate

Trigger a manual backup

docker exec db-backup dbbackup backup --job myapp

Migrating from v1

v2 replaces the pipe-delimited backups.conf with YAML (backups.yml). An automatic conversion command is provided:

docker run --rm -v /path/to/appdata:/config \
ghcr.io/greite/database-backup:2 migrate /config/backups.conf > backups.yml

Review the output, then mount it at /config/backups.yml.

See docs/MIGRATION-V2.md for the full field mapping and list of behavior changes.

Managing Backups

Backup file structure

Backups are organized by type and job name (<type>/<job-name>/). For configs migrated from v1 the job name equals the database name, so the paths look like:

backups/
├── postgres/
│ └── myapp_db/
│ ├── myapp_db_20250131_020000.sql.gz
│ ├── myapp_db_20250130_020000.sql.gz
│ └── ...
├── mariadb/
│ └── wordpress/
│ ├── wordpress_20250131_030000.sql.gz
│ └── ...
└── mongodb/
└── events/
├── events_20250131_000000.tar.gz
└── ...

Note: MongoDB backups use .tar.gz format (compressed BSON archive), while PostgreSQL and MariaDB use .sql.gz (compressed SQL dump). Encrypted backups get an additional .gpg or .age suffix.

Restore a backup

If at-rest encryption is enabled, first decrypt the file:

# GPG
gpg --batch --passphrase-file /path/to/passphrase \
--decrypt myapp_db_20250131_020000.sql.gz.gpg > myapp_db_20250131_020000.sql.gz
# age
age --decrypt -i /path/to/key myapp_db_20250131_020000.sql.gz.age > myapp_db_20250131_020000.sql.gz

PostgreSQL:

gunzip -c backups/postgres/myapp_db/myapp_db_20250131_020000.sql.gz | \
psql -h localhost -U postgres -d myapp_db

MariaDB:

gunzip -c backups/mariadb/wordpress/wordpress_20250131_030000.sql.gz | \
mysql -h localhost -u root -p wordpress

MongoDB:

mkdir -p /tmp/mongo_restore
tar -xzf backups/mongodb/events/events_20250131_000000.tar.gz -C /tmp/mongo_restore
mongorestore --uri="mongodb://admin:password@localhost:27017/events?authSource=admin" \
--gzip --drop /tmp/mongo_restore/events
rm -rf /tmp/mongo_restore

Security

Password handling

All passwords are written at startup to per-job files (root-only, mode 600) and are never passed on command lines — argv is world-readable through /proc/*/cmdline, so nothing sensitive ever appears in ps aux or docker top.

Prefer password_file over inline password in production; Docker secrets mount the file at /run/secrets/<name> automatically.

TLS connections

Set tls: true on a job (or under defaults) to encrypt the connection: PGSSLMODE=require for PostgreSQL, --ssl for MariaDB/MySQL, --ssl/--tls for MongoDB. Recommended when the database is not on the same private Docker network.

Best practices

  1. File permissions: chmod 600 backups.yml on the host (the container warns at startup if the mounted file is group/other-readable)

  2. Dedicated backup users: Create users with minimal privileges:

PostgreSQL:

CREATEUSERbackup_user WITH PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE myapp_db TO backup_user;
GRANTSELECTON ALL TABLES IN SCHEMA public TO backup_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANTSELECTON TABLES TO backup_user;

MariaDB:

CREATEUSER 'backup_user'@'%' IDENTIFIED BY 'secure_password';
GRANTSELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON myapp_db.* TO 'backup_user'@'%';
FLUSH PRIVILEGES;

MongoDB:

useadmindb.createUser({user: "backup_user",pwd: "secure_password",roles: [{role: "backup",db: "admin"},{role: "read",db: "myapp"}]})
  1. Backup storage: Consider mounting an encrypted volume for /backups

  2. Off-site backups: Regularly sync backups to external storage (S3, NAS, etc.)

Monitoring

The container includes a healthcheck that verifies all configured databases are reachable using native drivers. It runs every 5 minutes with a 30-second timeout.

# View healthcheck status
docker inspect --format='{{.State.Health.Status}}' db-backup
# View last healthcheck details
docker inspect --format='{{json .State.Health}}' db-backup | jq
# Run the healthcheck manually
docker exec db-backup dbbackup healthcheck

Dynamic client installation

No database client is pre-installed in the Docker image. At startup the container parses the configuration and installs only the required tools:

  • PostgreSQL: installs the specific configured version (pg_version, any version available in the PGDG repository, default 18)
  • MariaDB/MySQL: installs mariadb-client
  • MongoDB: installs mongodump and mongorestore

Internet access is required on first startup. First startup may take an extra 30–90 seconds depending on which clients need to be installed.

CI/CD with GitHub Actions

Release Build (.github/workflows/docker-build.yml)

Triggered on version tags (v*.*.*) or manual dispatch. Builds and pushes the Docker image to GitHub Container Registry with semantic versioning tags.

Base Image Update Check (.github/workflows/base-image-check.yml)

Runs automatically 4 times daily (at 00:00, 06:00, 12:00, and 18:00 UTC) to check if the debian:trixie-slim base image has been updated. If a new version is detected, it automatically rebuilds and pushes the image.

Creating a release

git tag -a v2.0.0 -m "Release version 2.0.0"
git push origin v2.0.0

This automatically creates the following Docker tags:

  • ghcr.io/greite/database-backup:v2.0.0
  • ghcr.io/greite/database-backup:2.0
  • ghcr.io/greite/database-backup:2
  • ghcr.io/greite/database-backup:latest

License

This project is licensed under the MIT License — see the LICENSE file for details.

About

Lightweight Go-powered Docker container for scheduled PostgreSQL, MariaDB/MySQL and MongoDB backups - rotation, gpg/age encryption, native healthchecks

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages