Production backup container for Torrust Tracker deployments. This container performs automated backups of MySQL/SQLite databases and configuration files.
- Database Backup: Supports MySQL (via mysqldump) and SQLite (via .backup command)
- Config File Backup: Archives specified configuration files and directories
- Compression: All backups are gzip-compressed to save storage
- Retention Management: Automatic cleanup of backups older than configured retention period
- Config-Driven: All behavior controlled via mounted configuration files (no environment variables)
- Tested: 58 unit tests run during container build
The container is configured via two mounted files:
Bash-sourceable configuration file with key=value pairs:
# Days to keep old backups before deletion
BACKUP_RETENTION_DAYS=7
# Path to file containing list of paths to backup
BACKUP_PATHS_FILE=/etc/backup/backup-paths.txt
# Database type: mysql, sqlite, or none
DB_TYPE=mysql
# MySQL configuration (required if DB_TYPE=mysql)
DB_HOST=mysql
DB_PORT=3306
DB_USER=tracker_user
DB_PASSWORD=tracker_password
DB_NAME=torrust_tracker
# SQLite configuration (required if DB_TYPE=sqlite)
DB_PATH=/data/storage/tracker/lib/tracker.dbPlain text file listing paths to backup (one per line):
# Comments start with #
/data/storage/tracker/etc/tracker.toml
/data/storage/prometheus/etc/prometheus.yml
/data/storage/grafana/provisioning
/data/storage/caddy/etc/Caddyfile
| Mount Point | Purpose | Mode |
|---|---|---|
/backups |
Output directory for all backups | Read-Write |
/data |
Source data directory (app storage) | Read-Only |
/etc/backup/backup.conf |
Main configuration file | Read-Only |
/etc/backup/backup-paths.txt |
Paths to backup | Read-Only |
/backups/
├── mysql/
│ ├── mysql_20260201_030000.sql.gz
│ └── mysql_20260202_030000.sql.gz
├── sqlite/
│ ├── sqlite_20260201_030000.db.gz
│ └── sqlite_20260202_030000.db.gz
└── config/
├── config_20260201_030000.tar.gz
└── config_20260202_030000.tar.gz
The container runs once and exits:
docker run --rm \
-v /path/to/backup.conf:/etc/backup/backup.conf:ro \
-v /path/to/backup-paths.txt:/etc/backup/backup-paths.txt:ro \
-v /opt/torrust/storage:/data:ro \
-v /opt/torrust/storage/backup:/backups \
torrust/backup:latestservices:
backup:
image: torrust/backup:latest
container_name: backup
restart: "no"
volumes:
- ./backup/backup.conf:/etc/backup/backup.conf:ro
- ./backup/backup-paths.txt:/etc/backup/backup-paths.txt:ro
- ./storage:/data:ro
- ./storage/backup:/backups
networks:
- database_network # Only if MySQL backup enabled
depends_on:
mysql:
condition: service_healthy # Only if MySQL backup enabled- Uses
mysqldumpwith--single-transactionfor consistent snapshots - Requires network connectivity to MySQL service
- Container must wait for MySQL to be healthy before starting
- Uses SQLite's
.backupcommand for online backup - No network required (file-based access via volume)
- Safe to run while tracker is accessing the database
- Container runs as non-root user (UID 1000, username:
torrust) - Backup files inherit the same ownership as application files
- Database credentials stored in config file (mounted read-only)
The backup container build uses a specific build context strategy to maintain consistency between local development and CI environments. This prevents subtle build failures caused by context/path mismatches.
Critical Concept: In Docker, COPY and ADD commands resolve paths relative to the build context, NOT relative to the Dockerfile location. Understanding this is essential to prevent regression.
cd /path/to/docker/backup
docker build -t torrust/backup:latest .Or from repository root:
# Correct: context is ./docker/backup directory
docker build -f docker/backup/Dockerfile -t torrust/backup:latest docker/backup
# Incorrect: would break COPY commands
docker build -f docker/backup/Dockerfile -t torrust/backup:latest .Key Point: When you run docker build -t image:tag <CONTEXT>, the <CONTEXT> path becomes the root for all COPY/ADD commands in the Dockerfile. Our Dockerfile uses simple relative paths (COPY backup.sh) which work when context is ./docker/backup.
The GitHub Actions workflow specifies build context explicitly:
uses: docker/build-push-action@v6
with:
context: ./docker/backup # Build context is the docker/backup directory
file: ./docker/backup/Dockerfile
# ...This ensures the CI build behaves identically to local builds.
Previous Regression (commit 9d297cc5):
- Workflow used
context: .(repository root) - Dockerfile had to use full paths:
COPY docker/backup/backup.sh /scripts/backup.sh - This was confusing and error-prone because paths looked like they were from the root
Current Approach:
- Workflow uses
context: ./docker/backup - Dockerfile uses natural relative paths:
COPY backup.sh /scripts/backup.sh - Both local builds and CI builds work identically
- Future developers won't accidentally change the context and break builds
Tests run automatically during container build:
docker build -t torrust/backup:latest .Build fails if any test fails. To run tests manually:
cd /path/to/docker/backup
bats backup_test.batsThe cleanup process:
- Runs after each backup cycle
- Finds backups older than
BACKUP_RETENTION_DAYS - Deletes old backups from all backup directories (mysql, sqlite, config)
- Logs count of deleted files
Example: With BACKUP_RETENTION_DAYS=7, backups older than 7 days are deleted.
Check configuration:
docker logs backupCommon issues:
- Config file not found
- Required variables missing (e.g.,
DB_HOSTfor MySQL) - Database file not found (for SQLite)
- Paths file not found
Ensure:
- MySQL service is healthy before backup starts
- Container is on the same network as MySQL
- Credentials are correct
- Database name exists
Ensure:
DB_PATHpoints to actual database file- Path is accessible from container (check volume mount)
- Database file is not corrupted
The container runs as UID 1000. Ensure:
- Host backup directory is writable by UID 1000
- Or adjust
BACKUP_UIDbuild arg when building image
docker build \
--build-arg BACKUP_UID=1000 \
--build-arg BACKUP_GID=1000 \
-t torrust/backup:latest \
.# Build image
docker build -t torrust/backup:test .
# Create test config
mkdir -p test-backup
cat > test-backup/backup.conf <<EOF
BACKUP_RETENTION_DAYS=7
DB_TYPE=none
EOF
# Run backup
docker run --rm \
-v $(pwd)/test-backup/backup.conf:/etc/backup/backup.conf:ro \
-v $(pwd)/test-backup:/backups \
torrust/backup:testSee LICENSE file in repository root.