All apps · 0 apps
database-backup
Docker app from GreiteTurtle's Repository
Overview
Lightweight Go-powered container to automate PostgreSQL, MariaDB/MySQL, and MongoDB backups on a schedule.
Features:
- Simple YAML configuration with strict validation and a built-in validate command
- Dynamic client install at startup: only the tools your config needs are installed
- Flexible cron scheduling per backup job, with graceful shutdown for in-flight backups
- Streaming gzip compression, atomic file writes, timestamped dumps
- Optional at-rest encryption: gpg-compatible OpenPGP or age (passphrase or public key)
- Configurable per-job retention; old backups are only rotated after a successful run
- Runs unprivileged (uid 1000) after client installation; secrets never appear in process arguments
- Built-in healthcheck using native database drivers (no heavy CLI downloads)
- Multiple databases (different types and hosts) handled by a single container
- Multi-architecture (amd64, arm64) and auto-rebuilt on base image security updates
Setup:
- Copy backups.yml.example from the project repository to your appdata directory and rename it to backups.yml
- Edit backups.yml with your databases (chmod 600, it contains credentials)
- Map the file to /config/backups.yml and a host folder to /backups
Upgrading from v1: run the built-in migrate command to convert your old backups.conf to YAML. See https://github.com/Greite/database-backup/blob/main/docs/MIGRATION-V2.md
Readme
View on GitHub
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
mongoshdownload) - Migration command to convert v1
backups.confto 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:latest
container_name: db-backup
restart: unless-stopped
volumes:
- ./backups:/backups
- ./backups.yml:/config/backups.yml:ro
networks:
- db-network
Available Tags
latest— Latest stable versionv2.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:
- Open the Apps tab in your Unraid web UI
- Search for
database-backup - 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); copybackups.yml.examplethere first and edit it - Timezone — IANA timezone name (default
Europe/Paris)
- Backups Storage — host path that will receive the dumps (default
- 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:
| Field | Required | Default | Description |
|---|---|---|---|
defaults.retention_days |
No | 7 |
Retention days applied to all jobs unless overridden |
defaults.tls |
No | false |
TLS flag applied to all jobs unless overridden |
shutdown_grace |
No | 5m |
Grace period for in-flight backups on container stop. Must be a duration string ("5m", "300s", "1h") — bare integers are rejected |
encryption |
No | — | At-rest encryption block (see below) |
jobs |
Yes | — | List of backup jobs |
Per-job fields:
| Field | Required | Default | Description |
|---|---|---|---|
name |
Yes | — | Unique job identifier (used in log output and directory paths) |
type |
Yes | — | postgres, mariadb, mysql, or mongodb |
host |
Yes | — | Database hostname or IP |
port |
No | 5432 / 3306 / 27017 | Connection port |
database |
Yes | — | Database name to back up |
user |
No* | — | Database user |
password |
No* | — | Inline password (any character, including |) |
password_file |
No* | — | Path to a file containing the password (e.g. a Docker secret) |
schedule |
Yes | — | Cron expression ("0 2 * * *") or shorthand ("@daily") |
retention_days |
No | from defaults or 7 |
Number of days to keep backups |
pg_version |
No | 18 |
PostgreSQL client version (any version available in the PGDG repository, default 18); PostgreSQL only |
tls |
No | from defaults or false |
true 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: gpg
passphrase_file: /run/secrets/backup_passphrase
# or: passphrase: my_passphrase (visible in docker inspect — avoid in production)
age (passphrase) — decrypt with age -d:
encryption:
method: age
passphrase_file: /run/secrets/backup_passphrase
age (public-key recipients) — the container never holds the private key:
encryption:
method: age
recipients:
- 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:latest
secrets:
- db_password
# In backups.yml: password_file: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Usage
Volumes
| Mount point | Purpose |
|---|---|
/config/backups.yml |
YAML configuration file (required) |
/backups |
Directory where backup files are written |
Subcommands
The container binary (dbbackup) supports the following subcommands. When no subcommand is given, run is the default.
| Subcommand | Description |
|---|---|
run |
Load config and start the built-in scheduler (default) |
validate |
Parse and validate the config, then exit; useful in CI |
backup --job <name> |
Run a single named job immediately (use with docker exec) |
healthcheck |
Check connectivity for all configured databases and exit |
migrate [path] |
Convert a v1 pipe-delimited file to YAML and print to stdout |
Docker Compose (recommended)
- Copy and edit the example config:
cp backups.yml.example backups.yml
chmod 600 backups.yml
# Edit backups.yml with your settings
- Start the services:
docker compose up -d
- 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.gzformat (compressed BSON archive), while PostgreSQL and MariaDB use.sql.gz(compressed SQL dump). Encrypted backups get an additional.gpgor.agesuffix.
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
File permissions:
chmod 600 backups.ymlon the host (the container warns at startup if the mounted file is group/other-readable)Dedicated backup users: Create users with minimal privileges:
PostgreSQL:
CREATE USER backup_user WITH PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE myapp_db TO backup_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO backup_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO backup_user;
MariaDB:
CREATE USER 'backup_user'@'%' IDENTIFIED BY 'secure_password';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON myapp_db.* TO 'backup_user'@'%';
FLUSH PRIVILEGES;
MongoDB:
use admin
db.createUser({
user: "backup_user",
pwd: "secure_password",
roles: [
{ role: "backup", db: "admin" },
{ role: "read", db: "myapp" }
]
})
Backup storage: Consider mounting an encrypted volume for
/backupsOff-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
mongodumpandmongorestore
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.0ghcr.io/greite/database-backup:2.0ghcr.io/greite/database-backup:2ghcr.io/greite/database-backup:latest
License
This project is licensed under the MIT License — see the LICENSE file for details.
Install database-backup on Unraid in a few clicks.
Find database-backup in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/greite/database-backup:latestRuntime arguments
- Network
bridge- Shell
bash- Privileged
- false
Template configuration
Host path where database dumps are written. Files are organized as type/job-name/job-name_timestamp.sql.gz (or .tar.gz for MongoDB), plus a .gpg or .age suffix when encryption is enabled.
- Target
- /backups
- Default
- /mnt/user/appdata/database-backup/backups
- Value
- /mnt/user/appdata/database-backup/backups
Path to the backups.yml file on the host. Get the example from https://github.com/Greite/database-backup/blob/main/backups.yml.example and edit it before starting the container. Upgrading from v1? Convert your backups.conf with the built-in migrate command (see the migration guide linked in the Overview).
- Target
- /config/backups.yml
- Default
- /mnt/user/appdata/database-backup/backups.yml
- Value
- /mnt/user/appdata/database-backup/backups.yml
Optional. Enables gpg-compatible at-rest encryption of every backup (decrypt with gpg -d). Leave empty to disable, or use the encryption block in backups.yml for more options (age, key files).
- Target
- BACKUP_ENCRYPTION_PASSPHRASE