22 KiB
Labelbase Backup and Migration Guide
A comprehensive guide for safely backing up your Labelbase database and running Django migrations.
Table of Contents
- Why Backup?
- Quick Backup
- Automated Backup Script
- Running Migrations Safely
- Upgrading Labelbase
- Restoring from Backup
- Scheduled Backups
- Best Practices
Why Backup?
Always backup before running migrations! Migrations can:
- Alter database table structures in irreversible ways
- Delete data if there are bugs in the migration code
- Fail mid-execution, leaving your database inconsistent
- Introduce conflicts with existing data
A backup takes 30 seconds. Recovery without one could take hours or days.
Quick Backup
From your Labelbase directory:
# Navigate to Labelbase directory
cd Labelbase
# Source environment variables
source exports.sh
# Create backup with timestamp
docker-compose exec -T labelbase_mysql mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase > backup_$(date +%Y%m%d_%H%M%S).sql
This creates a backup file like backup_20250119_143022.sql.
Backup config.ini (Important!)
The config.ini file contains encryption keys and other critical settings. Always back it up too:
# Navigate to Labelbase directory
cd Labelbase
# Source environment variables
source exports.sh
# Create backup with timestamp
docker-compose exec -T labelbase_django cat /app/config.ini > backup_$(date +%Y%m%d_%H%M%S)_config.ini
This creates a backup file like backup_20250119_143022_config.ini.
Automated Backup Script
Create a reusable backup script that handles everything automatically.
Create the Script
Save this as backup-labelbase.sh in your Labelbase directory:
#!/bin/bash
# Labelbase Database Backup Script
# Usage: ./backup-labelbase.sh
# Configuration
LABELBASE_DIR="/path/to/Labelbase" # CHANGE THIS to your actual path
BACKUP_DIR="$LABELBASE_DIR/backups"
KEEP_BACKUPS=10 # Number of backups to keep
# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Change to Labelbase directory
cd "$LABELBASE_DIR" || exit 1
# Source environment variables for MySQL passwords
if [ ! -f "exports.sh" ]; then
echo -e "${RED}✗ Error: exports.sh not found!${NC}"
echo "Make sure you're in the Labelbase directory and exports.sh exists."
exit 1
fi
source exports.sh
# Check if MySQL password is set
if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
echo -e "${RED}✗ Error: MYSQL_ROOT_PASSWORD not set!${NC}"
echo "Make sure exports.sh contains the MySQL password."
exit 1
fi
# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"
# Generate backup filename with timestamp
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/labelbase_backup_$TIMESTAMP.sql"
CONFIG_BACKUP_FILE="$BACKUP_DIR/config_backup_$TIMESTAMP.ini"
echo -e "${YELLOW}Starting backup...${NC}"
echo "Database backup: $BACKUP_FILE"
echo "Config backup: $CONFIG_BACKUP_FILE"
# Backup config.ini first (contains encryption keys!)
echo "Backing up config.ini..."
docker-compose exec -T labelbase_django cat /app/config.ini > "$CONFIG_BACKUP_FILE"
if [ $? -eq 0 ] && [ -s "$CONFIG_BACKUP_FILE" ]; then
echo -e "${GREEN}✓ Config backup successful${NC}"
else
echo -e "${YELLOW}⚠ Config backup failed or file is empty${NC}"
fi
# Create database backup
docker-compose exec -T labelbase_mysql mysqldump \
-u root \
-p"${MYSQL_ROOT_PASSWORD}" \
--single-transaction \
--quick \
--lock-tables=false \
labelbase > "$BACKUP_FILE"
# Check if backup was successful
if [ $? -eq 0 ] && [ -s "$BACKUP_FILE" ]; then
echo -e "${GREEN}✓ Backup successful!${NC}"
# Get file size
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
echo "Backup size: $SIZE"
# Compress backup to save space
echo "Compressing backup..."
gzip "$BACKUP_FILE"
if [ $? -eq 0 ]; then
COMPRESSED_SIZE=$(du -h "${BACKUP_FILE}.gz" | cut -f1)
echo -e "${GREEN}✓ Compressed to: $COMPRESSED_SIZE${NC}"
echo "Backup location: ${BACKUP_FILE}.gz"
else
echo -e "${YELLOW}⚠ Compression failed, keeping uncompressed backup${NC}"
fi
# Clean up old backups (keep only last N backups)
echo "Cleaning up old backups (keeping last $KEEP_BACKUPS)..."
BACKUP_COUNT=$(ls -1 "$BACKUP_DIR"/labelbase_backup_*.sql.gz 2>/dev/null | wc -l)
if [ "$BACKUP_COUNT" -gt "$KEEP_BACKUPS" ]; then
ls -t "$BACKUP_DIR"/labelbase_backup_*.sql.gz | tail -n +$((KEEP_BACKUPS + 1)) | xargs -r rm
echo -e "${GREEN}✓ Cleaned up old backups${NC}"
else
echo "No cleanup needed ($BACKUP_COUNT backups exist)"
fi
echo ""
echo -e "${GREEN}=== Backup Complete ===${NC}"
echo "Database: ${BACKUP_FILE}.gz"
echo "Config: ${CONFIG_BACKUP_FILE}"
else
echo -e "${RED}✗ Backup failed!${NC}"
# Remove empty or failed backup file
[ -f "$BACKUP_FILE" ] && rm "$BACKUP_FILE"
echo "Troubleshooting:"
echo "1. Check if MySQL container is running: docker-compose ps"
echo "2. Check MySQL logs: docker-compose logs labelbase_mysql"
echo "3. Verify password in exports.sh"
exit 1
fi
Make Script Executable
chmod +x backup-labelbase.sh
Edit Configuration
Open backup-labelbase.sh and change this line to your actual Labelbase path:
LABELBASE_DIR="/path/to/Labelbase" # CHANGE THIS!
For example:
LABELBASE_DIR="/root/Labelbase"
# or
LABELBASE_DIR="/home/username/Labelbase"
Run the Backup
./backup-labelbase.sh
You'll see output like:
Starting backup...
Backup file: /path/to/Labelbase/backups/labelbase_backup_20250119_143022.sql
✓ Backup successful!
Backup size: 15M
Compressing backup...
✓ Compressed to: 3.2M
Backup location: /path/to/Labelbase/backups/labelbase_backup_20250119_143022.sql.gz
Cleaning up old backups (keeping last 10)...
No cleanup needed (3 backups exist)
=== Backup Complete ===
Location: /path/to/Labelbase/backups/labelbase_backup_20250119_143022.sql.gz
Running Migrations Safely
Always follow this order:
Step 1: Create a Backup
./backup-labelbase.sh
Step 2: Check Migration Status
source exports.sh
docker-compose exec labelbase_django python manage.py showmigrations
This shows which migrations are applied (marked with [X]) and pending (marked with [ ]).
Step 3: Review Pending Migrations
Look for any unapplied migrations. If you see pending migrations for critical apps, review them carefully.
Step 4: Apply Migrations
# If you've modified models, create new migrations first
docker-compose exec labelbase_django python manage.py makemigrations
# Apply all pending migrations
docker-compose exec labelbase_django python manage.py migrate
Step 5: Verify Application
After migrations complete:
- Check for any error messages
- Visit your Labelbase site
- Test critical functionality
- Check Django logs:
docker-compose logs labelbase_django
Step 6: If Something Goes Wrong
If migrations fail or break functionality, restore from backup (see below).
Upgrading Labelbase
When new versions of Labelbase are released, follow this workflow to safely upgrade.
Complete Upgrade Workflow
Step 1: Backup First (Critical!)
cd Labelbase
./backup-labelbase.sh
Step 2: Pull Latest Code
git pull origin master
Step 3: Rebuild Containers (if needed)
If dependencies or Docker configuration changed:
source exports.sh && docker-compose up --build -d
Or use the main script:
source exports.sh && ./build-and-run-labelbase.sh
⚠️ IMPORTANT: These commands are SAFE - they rebuild containers but preserve your data in Docker volumes. Your database and uploaded files are NOT deleted.
❌ DANGER ZONE - Commands that DELETE data:
# NEVER run these unless you want to lose ALL data:
docker-compose down -v # The -v flag deletes volumes = data loss!
docker volume prune # Deletes unused volumes
docker system prune -a # Nuclear option - deletes everything
**Step 4: Apply Migrations and collect static files **
source exports.sh
docker-compose exec labelbase_django python manage.py showmigrations
docker-compose exec labelbase_django python manage.py migrate
docker-compose exec labelbase_django python manage.py collectstatic --noinput
Step 5: Restart Services
docker-compose restart labelbase_django
Step 6: Verify Everything Works
- Visit your Labelbase site
- Test critical functionality
- Check logs:
docker-compose logs -f labelbase_django
Quick Upgrade Script
Create update-and-migrate.sh for a streamlined upgrade process:
#!/bin/bash
# Labelbase Quick Update & Migration Script
# Usage: source exports.sh && ./update-and-migrate.sh
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo -e "${YELLOW}=== Labelbase Update & Migrate ===${NC}"
# Check if we're in the right directory
if [ ! -f "docker-compose.yml" ]; then
echo -e "${RED}Error: Not in Labelbase directory (docker-compose.yml not found)${NC}"
exit 1
fi
# Check env vars
if [[ -z "${MYSQL_ROOT_PASSWORD}" ]]; then
echo -e "${RED}Error: Run 'source exports.sh' first!${NC}"
exit 1
fi
# Step 1: Backup
echo -e "${YELLOW}Step 1: Creating backup...${NC}"
if [ -f "backup-labelbase.sh" ]; then
./backup-labelbase.sh
if [ $? -ne 0 ]; then
echo -e "${RED}Backup failed! Aborting upgrade.${NC}"
exit 1
fi
else
echo -e "${YELLOW}⚠ Warning: backup-labelbase.sh not found, skipping backup${NC}"
read -p "Continue without backup? (yes/no): " -r
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
echo "Upgrade cancelled."
exit 0
fi
fi
# Step 2: Pull latest code
echo ""
echo -e "${YELLOW}Step 2: Pulling latest changes...${NC}"
git pull origin master
if [ $? -ne 0 ]; then
echo -e "${RED}Git pull failed!${NC}"
exit 1
fi
# Step 3: Check for pending migrations
echo ""
echo -e "${YELLOW}Step 3: Checking for migrations...${NC}"
PENDING=$(docker-compose exec -T labelbase_django python manage.py showmigrations --plan 2>/dev/null | grep "\[ \]" | wc -l)
if [ $PENDING -gt 0 ]; then
echo -e "${YELLOW}Found $PENDING pending migration(s)${NC}"
# Show what will be migrated
echo "Pending migrations:"
docker-compose exec -T labelbase_django python manage.py showmigrations | grep "\[ \]"
echo ""
read -p "Apply migrations now? (y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Applying migrations..."
docker-compose exec -T labelbase_django python manage.py migrate --noinput
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ Migrations applied successfully${NC}"
else
echo -e "${RED}✗ Migrations failed!${NC}"
echo "Check logs: docker-compose logs labelbase_django"
exit 1
fi
else
echo -e "${YELLOW}⚠ Skipping migrations${NC}"
echo "Run manually later: docker-compose exec labelbase_django python manage.py migrate"
fi
else
echo -e "${GREEN}✓ No pending migrations${NC}"
fi
# Step 4: Restart Django
echo ""
echo -e "${YELLOW}Step 4: Restarting Django...${NC}"
docker-compose restart labelbase_django
echo ""
echo -e "${GREEN}=== Update Complete! ===${NC}"
echo "Check logs: docker-compose logs -f labelbase_django"
echo "Visit your site to verify everything works"
Make it executable:
chmod +x update-and-migrate.sh
Using the Quick Upgrade Script
# Navigate to Labelbase
cd Labelbase
# Source environment and run update
source exports.sh && ./update-and-migrate.sh
The script will:
- ✓ Create automatic backup
- ✓ Pull latest code from git
- ✓ Detect pending migrations
- ✓ Ask for confirmation before applying
- ✓ Restart services
- ✓ Provide verification steps
When to Rebuild vs. Restart
Just restart (docker-compose restart) when:
- Only Django code changed (Python files)
- No dependency updates
- No Dockerfile changes
- Fastest option
Full rebuild (docker-compose up --build -d) when:
- requirements.txt changed
- Dockerfile modified
- New system packages needed
- Docker configuration changed
Data Safety Note: Both restart and --build are SAFE - they preserve your data. Docker stores your database and files in volumes that persist across rebuilds.
If unsure, rebuild - it's safer and only takes a minute longer.
What Actually Deletes Data
Only these commands delete data (requires -v flag):
# DANGER: This deletes ALL data including database!
docker-compose down -v
# To safely stop without deleting data, use:
docker-compose down # Safe - keeps volumes
docker-compose stop # Safe - just stops containers
Rule of thumb: If you see -v flag, your data is at risk!
Rollback After Failed Upgrade
If something goes wrong:
# 1. Stop services
docker-compose down
# 2. Restore previous code
git reset --hard HEAD~1
# 3. Restore database
./restore-labelbase.sh backups/labelbase_backup_TIMESTAMP.sql.gz
# 4. Restart
source exports.sh && docker-compose up -d
Restoring from Backup
If something goes wrong, you can restore your database from a backup.
Quick Restore
# Navigate to Labelbase directory
cd Labelbase
# Source environment variables
source exports.sh
# Stop Django to prevent conflicts
docker-compose stop labelbase_django
# Decompress and restore backup
gunzip -c backups/labelbase_backup_20250119_143022.sql.gz | \
docker-compose exec -T labelbase_mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase
# Restart all services
docker-compose up -d
# Check logs
docker-compose logs -f labelbase_django
Restore Script (Optional)
Create restore-labelbase.sh:
#!/bin/bash
# Labelbase Database Restore Script
# Usage: ./restore-labelbase.sh <backup-file>
LABELBASE_DIR="/path/to/Labelbase" # CHANGE THIS
BACKUP_FILE="$1"
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Check if backup file provided
if [ -z "$BACKUP_FILE" ]; then
echo -e "${RED}✗ Error: No backup file specified${NC}"
echo "Usage: ./restore-labelbase.sh <backup-file>"
echo ""
echo "Available backups:"
ls -lh "$LABELBASE_DIR/backups/"*.sql.gz 2>/dev/null || echo "No backups found"
exit 1
fi
# Check if backup file exists
if [ ! -f "$BACKUP_FILE" ]; then
echo -e "${RED}✗ Error: Backup file not found: $BACKUP_FILE${NC}"
exit 1
fi
# Change to Labelbase directory
cd "$LABELBASE_DIR" || exit 1
# Source environment variables
source exports.sh
echo -e "${YELLOW}⚠ WARNING: This will overwrite your current database!${NC}"
echo "Backup file: $BACKUP_FILE"
read -p "Are you sure you want to continue? (yes/no): " -r
echo
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
echo "Restore cancelled."
exit 0
fi
echo "Stopping Django container..."
docker-compose stop labelbase_django
echo "Restoring database..."
# Check if file is compressed
if [[ "$BACKUP_FILE" == *.gz ]]; then
gunzip -c "$BACKUP_FILE" | \
docker-compose exec -T labelbase_mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase
else
cat "$BACKUP_FILE" | \
docker-compose exec -T labelbase_mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase
fi
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ Database restored successfully${NC}"
else
echo -e "${RED}✗ Restore failed${NC}"
exit 1
fi
echo "Restarting services..."
docker-compose up -d
# Optional: Restore config.ini if you have a backup from the same time
# CONFIG_FILE="${BACKUP_FILE%_backup_*}_config_backup_${BACKUP_FILE##*_backup_}"
# CONFIG_FILE="${CONFIG_FILE%.sql.gz}.ini"
# if [ -f "$CONFIG_FILE" ]; then
# echo "Found config backup: $CONFIG_FILE"
# read -p "Restore config.ini too? (y/n) " -n 1 -r
# echo
# if [[ $REPLY =~ ^[Yy]$ ]]; then
# cat "$CONFIG_FILE" | docker-compose exec -T labelbase_django bash -c "cat > /app/config.ini"
# echo -e "${GREEN}✓ Config restored${NC}"
# docker-compose restart labelbase_django
# fi
# fi
echo ""
echo -e "${GREEN}=== Restore Complete ===${NC}"
echo "Check logs with: docker-compose logs -f labelbase_django"
Make executable and configure:
chmod +x restore-labelbase.sh
# Edit LABELBASE_DIR in the script
Usage:
./restore-labelbase.sh backups/labelbase_backup_20250119_143022.sql.gz
Scheduled Backups
Using Cron (Linux/Unix)
Automate daily backups at 2 AM:
# Edit crontab
crontab -e
# Add this line (adjust path to your Labelbase directory)
0 2 * * * cd /path/to/Labelbase && ./backup-labelbase.sh >> /var/log/labelbase-backup.log 2>&1
This runs the backup script daily at 2:00 AM and logs output to /var/log/labelbase-backup.log.
Verify Cron Job
# List current cron jobs
crontab -l
# Check backup log
tail -f /var/log/labelbase-backup.log
Alternative: Weekly Backups
# Every Sunday at 3 AM
0 3 * * 0 cd /path/to/Labelbase && ./backup-labelbase.sh >> /var/log/labelbase-backup.log 2>&1
Best Practices
Before Migrations
- ✓ Always create a backup first
- ✓ Review what migrations will be applied
- ✓ Have a rollback plan ready
- ✓ Test migrations on a development copy if possible
- ✓ Schedule migrations during low-traffic periods
Backup Storage
- ✓ Keep backups in multiple locations
- ✓ Regularly test your restore process
- ✓ Keep at least 7-10 recent backups
- ✓ Store critical backups off-server (external drive, cloud storage)
- ✓ Monitor backup script success/failure
Security
- ✓ Protect
exports.sh- it contains database passwords - ✓ Secure backup files - they contain all your data
- ✓ Protect
config.inibackups - they contain encryption keys - ✓ Use appropriate file permissions:
chmod 600 exports.sh chmod 700 backups/ chmod 600 backups/*.sql.gz chmod 600 backups/*_config.ini
Regular Maintenance
- ✓ Run backups before any system updates
- ✓ Test restore process quarterly
- ✓ Monitor backup file sizes (unexpected changes may indicate issues)
- ✓ Keep backup logs for troubleshooting
Docker Data Safety
- ✓ SAFE commands (preserve data):
docker-compose up --build -d- Rebuild containersdocker-compose restart- Restart servicesdocker-compose down- Stop without deleting volumesdocker-compose stop- Pause containers
- ✓ DANGEROUS commands (delete data):
docker-compose down -v- ⚠️ Deletes ALL volumes/datadocker volume prune- ⚠️ Removes unused volumesdocker system prune -a- ⚠️ Nuclear option
- ✓ Remember: The
-vflag means "delete volumes" = data loss!
Troubleshooting
"MYSQL_ROOT_PASSWORD not set"
Cause: exports.sh not sourced or doesn't contain password
Solution:
# Check if exports.sh exists
ls -la exports.sh
# Source it
source exports.sh
# Verify password is set
echo $MYSQL_ROOT_PASSWORD
"Access denied for user 'root'"
Cause: Wrong password in exports.sh
Solution:
- Check MySQL container logs:
docker-compose logs labelbase_mysql - Verify password in
exports.shmatches what MySQL expects - If lost, you may need to reset MySQL root password
Backup File is Empty or Very Small
Cause: MySQL container not running or database empty
Solution:
# Check container status
docker-compose ps
# Check MySQL logs
docker-compose logs labelbase_mysql
# Verify database exists
docker-compose exec labelbase_mysql mysql -u root -p -e "SHOW DATABASES;"
Restore Fails with "Unknown Database"
Cause: Database doesn't exist in MySQL
Solution:
# Create database first
docker-compose exec labelbase_mysql mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS labelbase;"
# Then restore
./restore-labelbase.sh backups/your_backup.sql.gz
Complete Safe Migration Workflow
Here's the complete workflow combining backup and migration:
# 1. Navigate to Labelbase
cd Labelbase
# 2. Create backup
./backup-labelbase.sh
# 3. Source environment
source exports.sh
# 4. Check what migrations will run
docker-compose exec labelbase_django python manage.py showmigrations
# 5. Apply migrations
docker-compose exec labelbase_django python manage.py makemigrations
docker-compose exec labelbase_django python manage.py migrate
# 6. Check for errors
docker-compose logs labelbase_django | tail -50
# 7. Test your application
# Visit site and verify functionality
# 8. If problems occur, restore:
# ./restore-labelbase.sh backups/labelbase_backup_TIMESTAMP.sql.gz
Quick Command Reference
# Quick upgrade (recommended)
source exports.sh && ./update-and-migrate.sh
# Manual upgrade workflow
./backup-labelbase.sh
git pull origin master
source exports.sh && docker-compose up --build -d
docker-compose exec labelbase_django python manage.py migrate
docker-compose restart labelbase_django
# Create backup (database + config.ini)
./backup-labelbase.sh
# Manual database backup
docker-compose exec -T labelbase_mysql mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase > backup_$(date +%Y%m%d_%H%M%S).sql
# Manual config.ini backup
docker-compose exec -T labelbase_django cat /app/config.ini > backup_$(date +%Y%m%d_%H%M%S)_config.ini
# List backups
ls -lh backups/
# Check migration status
docker-compose exec labelbase_django python manage.py showmigrations
# Apply migrations
docker-compose exec labelbase_django python manage.py migrate
# Restore backup
./restore-labelbase.sh backups/labelbase_backup_20250119_143022.sql.gz
# View recent Django logs
docker-compose logs labelbase_django | tail -100
# Access MySQL directly
docker-compose exec labelbase_mysql mysql -u root -p labelbase
Additional Resources
Support
If you encounter issues:
- Check the troubleshooting section above
- Review Docker logs:
docker-compose logs -f - Verify all services are running:
docker-compose ps - Check Labelbase GitHub issues: https://github.com/Labelbase/Labelbase/issues
Remember: A backup today saves recovery tomorrow. Always backup before migrations!