Backup and Restore
Protect your license data with comprehensive backup strategies and tested restore procedures.
Critical: Test Your Backups
Backups are only valuable if they can be restored. Regularly test your restore procedures in a staging environment.
Backup Strategy Overview
A robust backup strategy includes multiple layers:
| Backup Type | Frequency | Retention | Purpose |
|---|---|---|---|
| Database Dump | Daily | 30 days | Quick recovery, point-in-time restore |
| Volume Snapshot | Daily | 14 days | Fast recovery, full data copy |
| Off-site Backup | Weekly | 90 days | Disaster recovery |
| WAL Archive | Continuous | 7 days | Point-in-time recovery |
Quick Backup Commands
For immediate one-off backups:
PostgreSQL Database
# Full database dump
docker exec licenz-postgres pg_dumpall -U postgres > backup.sql
# Compressed backup
docker exec licenz-postgres pg_dumpall -U postgres | gzip > backup.sql.gz
# Single database backup
docker exec licenz-postgres pg_dump -U licenz licenz > licenz_only.sql Redis Data
# Trigger Redis save
docker exec licenz-redis redis-cli BGSAVE
# Copy Redis dump file
docker cp licenz-redis:/data/dump.rdb ./redis_backup.rdb Docker Volumes
# Backup PostgreSQL volume
docker run --rm -v licenz-postgres-data:/source:ro -v $(pwd):/backup \
alpine tar czf /backup/postgres_volume.tar.gz -C /source .
# Backup Redis volume
docker run --rm -v licenz-redis-data:/source:ro -v $(pwd):/backup \
alpine tar czf /backup/redis_volume.tar.gz -C /source . Automated Backup Script
Use this comprehensive backup script for production environments:
#!/bin/bash
# Licenz Backup Script
# Save as: backup-licenz.sh
set -e
# Configuration
BACKUP_DIR="/backups/licenz"
RETENTION_DAYS=${BACKUP_RETENTION_DAYS:-30}
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="licenz_backup_${TIMESTAMP}"
# Create backup directory
mkdir -p "${BACKUP_DIR}"
echo "Starting Licenz backup: ${BACKUP_NAME}"
# Backup PostgreSQL databases
echo "Backing up PostgreSQL..."
docker exec licenz-postgres pg_dumpall -U postgres | gzip > "${BACKUP_DIR}/${BACKUP_NAME}_postgres.sql.gz"
# Backup Redis data
echo "Backing up Redis..."
docker exec licenz-redis redis-cli BGSAVE
sleep 2
docker cp licenz-redis:/data/dump.rdb "${BACKUP_DIR}/${BACKUP_NAME}_redis.rdb"
# Backup Docker volumes
echo "Backing up volumes..."
docker run --rm \
-v licenz-postgres-data:/source:ro \
-v "${BACKUP_DIR}":/backup \
alpine tar czf "/backup/${BACKUP_NAME}_postgres_volume.tar.gz" -C /source .
docker run --rm \
-v licenz-redis-data:/source:ro \
-v "${BACKUP_DIR}":/backup \
alpine tar czf "/backup/${BACKUP_NAME}_redis_volume.tar.gz" -C /source .
# Backup environment configuration
echo "Backing up configuration..."
cp .env "${BACKUP_DIR}/${BACKUP_NAME}_env.backup"
# Create manifest
cat > "${BACKUP_DIR}/${BACKUP_NAME}_manifest.json" << EOF
{
"timestamp": "${TIMESTAMP}",
"backup_name": "${BACKUP_NAME}",
"components": [
"postgres.sql.gz",
"redis.rdb",
"postgres_volume.tar.gz",
"redis_volume.tar.gz",
"env.backup"
],
"licenz_version": "$(docker inspect licenz-api --format='{{.Config.Image}}')"
}
EOF
# Cleanup old backups
echo "Cleaning up old backups..."
find "${BACKUP_DIR}" -name "licenz_backup_*" -mtime +${RETENTION_DAYS} -delete
echo "Backup complete: ${BACKUP_DIR}/${BACKUP_NAME}"
ls -lh "${BACKUP_DIR}/${BACKUP_NAME}"* Installation
# Save the script
sudo nano /opt/licenz/backup-licenz.sh
# Make executable
sudo chmod +x /opt/licenz/backup-licenz.sh
# Create backup directory
sudo mkdir -p /backups/licenz
# Run manually
sudo /opt/licenz/backup-licenz.sh Scheduled Backups
Set up automated backups using cron:
# /etc/cron.d/licenz-backup
# Daily backup at 2 AM
0 2 * * * root /opt/licenz/backup-licenz.sh >> /var/log/licenz-backup.log 2>&1
# Weekly full backup on Sundays at 3 AM
0 3 * * 0 root /opt/licenz/full-backup-licenz.sh >> /var/log/licenz-backup.log 2>&1 Verify Cron Job
# Check cron logs
tail -f /var/log/licenz-backup.log
# List scheduled backups
ls -la /backups/licenz/ Restore Procedures
Automated Restore Script
#!/bin/bash
# Licenz Restore Script
# Save as: restore-licenz.sh
set -e
if [ -z "$1" ]; then
echo "Usage: ./restore-licenz.sh <backup_name>"
echo "Example: ./restore-licenz.sh licenz_backup_20240115_120000"
exit 1
fi
BACKUP_DIR="/backups/licenz"
BACKUP_NAME="$1"
echo "Restoring from backup: ${BACKUP_NAME}"
# Verify backup exists
if [ ! -f "${BACKUP_DIR}/${BACKUP_NAME}_manifest.json" ]; then
echo "Error: Backup manifest not found"
exit 1
fi
# Stop services
echo "Stopping services..."
docker compose down
# Restore PostgreSQL volume
echo "Restoring PostgreSQL volume..."
docker volume rm licenz-postgres-data 2>/dev/null || true
docker volume create licenz-postgres-data
docker run --rm \
-v licenz-postgres-data:/target \
-v "${BACKUP_DIR}":/backup:ro \
alpine sh -c "tar xzf /backup/${BACKUP_NAME}_postgres_volume.tar.gz -C /target"
# Restore Redis volume
echo "Restoring Redis volume..."
docker volume rm licenz-redis-data 2>/dev/null || true
docker volume create licenz-redis-data
docker run --rm \
-v licenz-redis-data:/target \
-v "${BACKUP_DIR}":/backup:ro \
alpine sh -c "tar xzf /backup/${BACKUP_NAME}_redis_volume.tar.gz -C /target"
# Restore environment (optional - review first)
echo "Environment backup available at: ${BACKUP_DIR}/${BACKUP_NAME}_env.backup"
echo "Review and manually restore if needed."
# Start services
echo "Starting services..."
docker compose up -d
# Wait for services to be healthy
echo "Waiting for services to be healthy..."
sleep 30
# Verify restoration
echo "Verifying restoration..."
curl -f http://localhost:8080/health || echo "Warning: API health check failed"
echo "Restore complete!" Manual Restore Steps
1. Stop Services
cd /opt/licenz
docker compose down 2. Restore PostgreSQL
# From SQL dump
docker compose up -d postgres
sleep 10
gunzip < backup.sql.gz | docker exec -i licenz-postgres psql -U postgres
# From volume backup
docker volume rm licenz-postgres-data
docker volume create licenz-postgres-data
docker run --rm -v licenz-postgres-data:/target -v $(pwd):/backup \
alpine tar xzf /backup/postgres_volume.tar.gz -C /target 3. Restore Redis
# From RDB file
docker volume rm licenz-redis-data
docker volume create licenz-redis-data
docker run --rm -v licenz-redis-data:/data -v $(pwd):/backup \
alpine cp /backup/redis_backup.rdb /data/dump.rdb 4. Start Services
docker compose up -d
# Verify services
docker compose ps
curl http://localhost:8080/health Off-site Backups
Store backups in cloud storage for disaster recovery:
AWS S3
#!/bin/bash
# S3 Backup Upload Script
# Requires: aws-cli configured with appropriate credentials
BACKUP_DIR="/backups/licenz"
S3_BUCKET="s3://your-backup-bucket/licenz"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Create local backup first
./backup-licenz.sh
# Upload to S3
LATEST_BACKUP=$(ls -t ${BACKUP_DIR}/licenz_backup_*_manifest.json | head -1 | sed 's/_manifest.json//')
BACKUP_NAME=$(basename ${LATEST_BACKUP})
aws s3 cp "${BACKUP_DIR}/${BACKUP_NAME}_postgres.sql.gz" "${S3_BUCKET}/${BACKUP_NAME}/"
aws s3 cp "${BACKUP_DIR}/${BACKUP_NAME}_redis.rdb" "${S3_BUCKET}/${BACKUP_NAME}/"
aws s3 cp "${BACKUP_DIR}/${BACKUP_NAME}_postgres_volume.tar.gz" "${S3_BUCKET}/${BACKUP_NAME}/"
aws s3 cp "${BACKUP_DIR}/${BACKUP_NAME}_redis_volume.tar.gz" "${S3_BUCKET}/${BACKUP_NAME}/"
aws s3 cp "${BACKUP_DIR}/${BACKUP_NAME}_manifest.json" "${S3_BUCKET}/${BACKUP_NAME}/"
echo "Backup uploaded to S3: ${S3_BUCKET}/${BACKUP_NAME}/" Restore from S3
# Download backup from S3
aws s3 sync s3://your-backup-bucket/licenz/licenz_backup_20240115_120000/ /backups/licenz/
# Run restore script
./restore-licenz.sh licenz_backup_20240115_120000 Point-in-Time Recovery
Enable WAL archiving for granular recovery options:
# Enable WAL archiving for point-in-time recovery
# Add to postgresql.conf or docker-compose environment
# In docker-compose.yml, add to postgres service:
environment:
- POSTGRES_INITDB_ARGS=--data-checksums
command: >
postgres
-c wal_level=replica
-c archive_mode=on
-c archive_command='gzip < %p > /backups/wal/%f.gz'
-c archive_timeout=60
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backups/wal:/backups/wal Recover to Specific Time
# Stop PostgreSQL
docker compose stop postgres
# Create recovery.conf
cat > recovery.conf << EOF
restore_command = 'gunzip < /backups/wal/%f.gz > %p'
recovery_target_time = '2024-01-15 14:30:00'
recovery_target_action = 'promote'
EOF
# Copy to PostgreSQL data directory
docker cp recovery.conf licenz-postgres:/var/lib/postgresql/data/
# Start PostgreSQL (will recover to specified time)
docker compose start postgres Disaster Recovery Plan
DR Checklist
- Document your backup procedures
- Test restore procedures quarterly
- Maintain off-site backup copies
- Keep environment configurations versioned
- Document recovery time objectives (RTO)
- Document recovery point objectives (RPO)
Recovery Time Estimates
| Scenario | Recovery Method | Estimated Time |
|---|---|---|
| Container crash | Docker restart | 1-2 minutes |
| Volume corruption | Volume restore | 5-15 minutes |
| Host failure | Full restore on new host | 30-60 minutes |
| Data center outage | Off-site restore | 1-4 hours |
Monitoring Backups
Set up monitoring to ensure backups complete successfully:
# Check latest backup age
LATEST=$(ls -t /backups/licenz/licenz_backup_*_manifest.json 2>/dev/null | head -1)
if [ -z "$LATEST" ]; then
echo "CRITICAL: No backups found"
exit 2
fi
AGE=$(( ($(date +%s) - $(stat -c %Y "$LATEST")) / 3600 ))
if [ $AGE -gt 24 ]; then
echo "WARNING: Latest backup is $AGE hours old"
exit 1
fi
echo "OK: Latest backup is $AGE hours old" Backup Verification
Periodically verify backup integrity:
# Test PostgreSQL backup integrity
gunzip -t backup.sql.gz && echo "PostgreSQL backup OK"
# Test volume archive integrity
tar tzf postgres_volume.tar.gz > /dev/null && echo "Volume backup OK"
# Test restore in isolated environment
docker compose -f docker-compose.test.yml up -d
./restore-licenz.sh licenz_backup_latest
curl http://localhost:8081/health