Backup container for systems that need to have the database and file system backed up
100K+
A minimal Alpine-based container to back up directories and databases on a schedule, with notification support via Nextcloud Talk Bot and e-mail.
Runs as non-root (uid 1000), uses supercronic, supports Docker secrets (*_FILE), optional age encryption, and a status-file healthcheck.
tar.gz, optional .age).sql.gz, optional .age)RUN_ON_START=on)| Variable | Description | Default |
|---|---|---|
FILES_TO_BACKUP | Space-separated list of folders to back up | /data |
BACKUP_DIR | Root folder for all backups | /backup |
LOG_DIR | Log file location | /logs |
RETENTION_DAYS | Days to keep old backups | 30 |
CRON_SCHEDULE | Cron expression (5 fields) | falls back to BACKUP_INTERVAL |
BACKUP_INTERVAL | Legacy alias for CRON_SCHEDULE | 0 * * * * |
RUN_ON_START | Run one backup before starting cron | off |
STACK_NAME | Logical stack name | — |
BACKUP_NAME | Friendly container/service name | hostname |
| Variable | Description |
|---|---|
BACKUP_ENCRYPT=on | Encrypt each artifact with age |
BACKUP_AGE_RECIPIENT | age recipient (age1...) |
BACKUP_AGE_RECIPIENT_FILE | Read recipient from file (Docker secret) |
Decrypt later with your private key: age -d -i key.txt backup.tar.gz.age > backup.tar.gz
*_FILE)Any of these can be provided as a file instead of a plain env value:
POSTGRES_PASSWORD_FILEMARIADB_PASSWORD_FILEMYSQL_PASSWORD_FILESMTP_PASS_FILENC_TALK_BOT_SECRET_FILEBACKUP_AGE_RECIPIENT_FILESet DB_TYPE=postgres, mariadb, mysql, or none
| Variable | Description |
|---|---|
POSTGRES_HOST | Database host |
POSTGRES_PORT | Port (default 5432) |
POSTGRES_USER | Username |
POSTGRES_PASSWORD / _FILE | Password |
POSTGRES_DATABASE | DB name |
POSTGRES_EXTRA_OPTS | Extra pg_dump flags (validated) |
| Variable | Description |
|---|---|
MARIADB_HOST | Database host |
MARIADB_PORT | Port (default 3306) |
MARIADB_USER | Username |
MARIADB_PASSWORD / _FILE | Password |
MARIADB_DATABASE | DB name |
MARIADB_EXTRA_OPTS | Extra dump flags (validated) |
| Variable | Description |
|---|---|
MYSQL_HOST | Database host |
MYSQL_PORT | Port (default 3306) |
MYSQL_USER | Username |
MYSQL_PASSWORD / _FILE | Password |
MYSQL_DATABASE | DB name |
MYSQL_EXTRA_OPTS | Extra dump flags (validated) |
| Variable | Description |
|---|---|
NC_TALK_NOTIFY=on | Enable Talk Bot notifications |
NC_TALK_URL | Nextcloud base URL |
NC_TALK_BOT_CHANNEL | Bot ID or room token |
NC_TALK_BOT_SECRET / _FILE | Shared secret |
| Variable | Description |
|---|---|
EMAIL_NOTIFY=on | Enable e-mail alerts |
EMAIL_TO | Recipient |
EMAIL_FROM | Sender |
SMTP_SERVER | SMTP hostname/IP |
SMTP_PORT | Port (default 587) |
SMTP_USER | SMTP user (if auth needed) |
SMTP_PASS / _FILE | SMTP password |
SMTP_TLS=on | Enable TLS (default on) |
SMTP config is written only when EMAIL_NOTIFY=on.
| Variable | Description | Default |
|---|---|---|
BACKUP_STATUS_FILE | Success marker path | /backup/.last_success |
HEALTHCHECK_MAX_AGE_SECONDS | Max age of a successful marker | 90000 (~25h) |
HEALTHCHECK_INITIAL_GRACE_SECONDS | After start, stay healthy without a fresh marker while waiting for cron (supercronic must be PID 1) | 93600 (~26h) |
The healthcheck is healthy when:
.last_success exists and is newer than HEALTHCHECK_MAX_AGE_SECONDS, orHEALTHCHECK_INITIAL_GRACE_SECONDS and supercronic is runningThat way Swarm/Compose do not restart-loop on deploy when RUN_ON_START=off and the next run is only at the cron time (e.g. midnight). After the grace period without any successful backup, the check becomes unhealthy.
Recommended start_period for the Docker healthcheck: a few minutes (startup only), not the full wait until cron.
docker-compose.ymlservices:
backup:
image: onesystems/dockerbackup:latest
user: "1000:1000"
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs:
- /tmp:size=256m,mode=1777
- /home/backup/config:size=1m,uid=1000,gid=1000,mode=0700
environment:
FILES_TO_BACKUP: "/data"
BACKUP_DIR: "/backup"
LOG_DIR: "/logs"
RETENTION_DAYS: 30
CRON_SCHEDULE: "0 * * * *"
BACKUP_NAME: "web-backup-db"
DB_TYPE: "postgres"
POSTGRES_HOST: "postgres"
POSTGRES_USER: "backupuser"
POSTGRES_PASSWORD_FILE: "/run/secrets/postgres_password"
POSTGRES_DATABASE: "myapp"
BACKUP_ENCRYPT: "on"
BACKUP_AGE_RECIPIENT_FILE: "/run/secrets/age_recipient"
secrets:
- postgres_password
- age_recipient
volumes:
- ./data:/data:ro
- ./backup:/backup
- ./logs:/logs
healthcheck:
test: ["CMD", "/scripts/healthcheck.sh"]
interval: 5m
timeout: 10s
retries: 3
start_period: 2m
secrets:
postgres_password:
file: ./secrets/postgres_password
age_recipient:
file: ./secrets/age_recipient
Ensure host dirs for /backup and /logs are writable by UID/GID 1000.
/backup/
.last_success
2025-04/
files_data_2025-04-30_1400.tar.gz[.age]
postgres_myapp_2025-04-30_1400.sql.gz[.age]
/logs/
backup_2025-04-30.log
Backups older than RETENTION_DAYS are removed automatically. Log entries show deleted files.
The backup script writes /backup/.last_success only on a fully successful run.
HEALTHCHECK_INITIAL_GRACE_SECONDS after start and while supercronic runs → still healthy (waiting for cron)No RUN_ON_START required for Swarm/Compose stability.
MIT License – free for commercial and private use.
Content type
Image
Digest
sha256:6aec9b1b8…
Size
30.9 MB
Last updated
about 1 month ago
docker pull onesystems/dockerbackup