New: Offline-first licensing with cryptographic validation. Learn more

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

Next Steps