A PHP CLI tool for automated MySQL/MariaDB database backups with intelligent deduplication and compression.
- ✅ Automated backups of all databases (excluding system databases)
- ✅ Intelligent deduplication - skips backups if database content hasn't changed (SHA1 hash comparison)
- ✅ Automatic compression - gzip compression of backup files
- ✅ Organized storage - separate directories for each database
- ✅ Cron-ready - designed for scheduled execution
- ✅ Docker support - includes Dockerfile for containerized usage
- ✅ Flexible configuration - INI file or environment variables
- PHP 8.0 or higher (for native installation)
mysqliPHP extension (for native installation)- Docker (for containerized installation - recommended)
- MySQL or MariaDB server
- System executables:
mysqldump,gzip,gunzip,sha1sum(included in Docker image)
If using Docker (recommended), ensure your user has Docker permissions:
# Add your user to the docker group
sudo usermod -aG docker your_username
# Log out and back in, or run:
newgrp docker
# Verify Docker access (should work without sudo)
docker psPick the option that matches your setup:
| Scenario | User Setup | Network Mode | Best For |
|---|---|---|---|
| A: Localhost MySQL | 'backup'@'localhost' |
--network host |
Single server, MySQL on localhost (simplest) |
| B: Isolated Container | 'backup'@'%' |
bridge (default) |
Better isolation, any Docker setup |
| C: Multi-Container | 'backup'@'%' |
Custom network | MySQL in another container |
Best for: MySQL/MariaDB running on the same server as Docker
-- Log in to MySQL/MariaDB as root
sudo mysql
-- Create backup user for localhost connections
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'your_secure_password';
-- Grant necessary permissions for full database backups
GRANT SELECT, LOCK TABLES, SHOW VIEW, TRIGGER, EVENT ON *.* TO 'backup'@'localhost';
GRANT SELECT ON mysql.proc TO 'backup'@'localhost';
FLUSH PRIVILEGES;
EXIT;
-- Test it
mysql -u backup -pConfiguration (.env):
DOCKER_NETWORK_MODE=host
DB_HOST=127.0.0.1
DB_USERNAME=backup
DB_PASSWORD=your_secure_passwordImportant: Use 127.0.0.1, NOT localhost! See .env.example for explanation.
Security: Most secure - only accepts connections from localhost
Best for: When you want container isolation or don't want to use --network host
sudo mysql
-- Create backup user that accepts connections from any IP
-- Still secure because it requires password and MySQL should be firewalled
CREATE USER 'backup'@'%' IDENTIFIED BY 'your_strong_password';
-- Or restrict to Docker networks only (more restrictive)
-- CREATE USER 'backup'@'172.%' IDENTIFIED BY 'your_strong_password';
GRANT SELECT, LOCK TABLES, SHOW VIEW, TRIGGER, EVENT ON *.* TO 'backup'@'%';
GRANT SELECT ON mysql.proc TO 'backup'@'%';
FLUSH PRIVILEGES;
EXIT;Configuration (.env):
DOCKER_NETWORK_MODE=bridge
DB_HOST=172.17.0.1 # Docker bridge gateway (usually stable)
# Or on some systems: DB_HOST=host.docker.internal
DB_USERNAME=backup
DB_PASSWORD=your_strong_passwordSecurity: Good - relies on password auth and firewall. Ensure MySQL is not exposed to internet!
Best for: MySQL running in another Docker container
-- Same as Scenario B
CREATE USER 'backup'@'%' IDENTIFIED BY 'your_strong_password';
GRANT SELECT, LOCK TABLES, SHOW VIEW, TRIGGER, EVENT ON *.* TO 'backup'@'%';
GRANT SELECT ON mysql.proc TO 'backup'@'%';
FLUSH PRIVILEGES;Configuration (.env):
DOCKER_NETWORK_MODE=your_mysql_network_name
DB_HOST=mysql_container_name # Name of your MySQL container
DB_USERNAME=backup
DB_PASSWORD=your_strong_passwordThe minimum permissions needed for backups:
-- Read all tables
GRANT SELECT ON *.* TO 'backup'@'...';
-- Lock tables during backup (for consistency)
GRANT LOCK TABLES ON *.* TO 'backup'@'...';
-- Backup views
GRANT SHOW VIEW ON *.* TO 'backup'@'...';
-- Backup triggers
GRANT TRIGGER ON *.* TO 'backup'@'...';
-- Backup events (scheduled tasks)
GRANT EVENT ON *.* TO 'backup'@'...';
-- Backup stored procedures/functions
GRANT SELECT ON mysql.proc TO 'backup'@'...';Complete setup in 5 minutes for localhost MySQL:
# 1. Ensure user has Docker permissions
sudo usermod -aG docker $USER
newgrp docker # Or log out/in
# 2. Create backup user in MariaDB/MySQL
sudo mysql <<EOF
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'your_secure_password';
GRANT SELECT, LOCK TABLES, SHOW VIEW, TRIGGER, EVENT ON *.* TO 'backup'@'localhost';
GRANT SELECT ON mysql.proc TO 'backup'@'localhost';
FLUSH PRIVILEGES;
EOF
# 3. Clone and setup
git clone https://github.com/indiehd/mysql-db-backup.git
cd mysql-db-backup
git checkout composer # Or master after merge
# 4. Build Docker image
docker build -t mysql-db-backup -f docker/Dockerfile .
# 5. Configure credentials
cat > .env <<EOF
DOCKER_NETWORK_MODE=host
DB_HOST=127.0.0.1
DB_USERNAME=backup
DB_PASSWORD=your_secure_password
EOF
chmod 600 .env
# 6. Test backup
./run-backup.sh
# 7. Schedule daily backups (2 AM)
crontab -e
# Add: 0 2 * * * /path/to/mysql-db-backup/run-backup.sh >> /var/log/mysql-backup.log 2>&1
# 8. (Optional) Add retention - keep 30 days
# Add: 0 3 * * 0 find /path/to/mysql-db-backup/backups -name "*.sql.gz" -mtime +30 -deleteDone! Your backups will run daily at 2 AM. ✅
composer require indiehd/mysql-db-backupgit clone https://github.com/indiehd/mysql-db-backup.git
cd mysql-db-backup
composer installCopy the example configuration file and customize it:
cp db-backup.ini.example db-backup.iniEdit db-backup.ini:
[connection]
hostname = localhost
username = your_mysql_user
password = your_mysql_password
[backup]
dumpdir = /path/to/backupsNote: The password field is optional if you're using socket-based authentication (e.g., auth_socket plugin in MariaDB).
You can also configure using environment variables:
export DB_HOST=127.0.0.1 # Use 127.0.0.1, not localhost (for Docker)
export DB_USERNAME=your_mysql_user
export DB_PASSWORD=your_mysql_password
export BACKUP_DIR=/path/to/backupsIf installed via Composer:
./vendor/bin/mysql-db-backupIf installed globally or from project directory:
./bin/mysql-db-backupTo run automated backups daily at 2 AM:
crontab -eAdd this line:
0 2 * * * cd /path/to/project && ./vendor/bin/mysql-db-backup >> /var/log/mysql-backup.log 2>&1Or if using environment variables:
0 2 * * * DB_HOST=localhost DB_USERNAME=backup_user DB_PASSWORD=secret BACKUP_DIR=/backups /path/to/vendor/bin/mysql-db-backup >> /var/log/mysql-backup.log 2>&1For complete Docker deployment guide, see DOCKER.md
# 1. Clone and build
git clone https://github.com/indiehd/mysql-db-backup.git
cd mysql-db-backup
git checkout composer
docker build -t mysql-db-backup -f docker/Dockerfile .
# 2. Create environment file (configure based on your scenario - see Database User Setup above)
cp .env.example .env
nano .env # Edit with your credentials and network mode
chmod 600 .env
# 3. Test one-time backup
./run-backup.sh
# 4. Set up scheduled backups with cron
crontab -e
# Add: 0 2 * * * /path/to/mysql-db-backup/run-backup.sh >> /var/log/mysql-backup.log 2>&1The run-backup.sh script automatically handles different network modes based on your .env configuration.
Scenario A - Host Network:
docker run --rm --network host --env-file .env \
-v $(pwd)/backups:/backups mysql-db-backupScenario B - Bridge Network:
docker run --rm --env-file .env \
-v $(pwd)/backups:/backups mysql-db-backupScenario C - Custom Network:
docker run --rm --network your_network --env-file .env \
-v $(pwd)/backups:/backups mysql-db-backup# Edit .env file first, then:
docker-compose up- Connect to MySQL/MariaDB server
- List all databases (excluding system databases:
mysql,information_schema,performance_schema,sys) - Dump each database using
mysqldumpwith optimized flags - Compare the new backup hash with the previous backup
- Skip if identical (no changes detected)
- Compress with gzip if new data is present
- Store in organized directory structure:
<dumpdir>/<database>/<YYYYMMDDHHmm>.sql.gz
The tool uses SHA1 hash comparison to avoid storing duplicate backups when database content hasn't changed. This saves significant storage space for databases that don't change frequently.
The dump files are created without comments (via --skip-comments) to ensure hash comparison works correctly, as comments include timestamps that would always differ.
/path/to/backups/
├── database1/
│ ├── 202602251400.sql.gz
│ ├── 202602261400.sql.gz
│ └── 202602271400.sql.gz
├── database2/
│ ├── 202602251400.sql.gz
│ └── 202602271400.sql.gz # 26th skipped - no changes
└── database3/
└── 202602251400.sql.gz
Important: This tool does NOT automatically delete old backups. Backups will accumulate over time.
To prevent unlimited backup growth, add a retention policy:
# Keep backups for 30 days (weekly cleanup)
0 3 * * 0 find /path/to/backups -name "*.sql.gz" -mtime +30 -deleteCommon retention periods:
- Development: 7-14 days
- Production: 30-90 days
- Compliance: May require longer (consult your requirements)
Future Enhancement: Built-in retention policies are planned for a future release.
Ensure the backup directory is writable:
chmod 755 /path/to/backupsTest your connection:
mysql -h localhost -u your_user -pVerify required executables are available:
which mysqldump gzip gunzip sha1sumGNU General Public License v3.0 or later (GPL-3.0-or-later)
Ben Johnson (ben@indiehd.com)
Copyright (c) 2012-2026, Ben Johnson