T
Tenanto
Documentation / Backup Restore

Backup Restore

Updated Jan 25, 2026

Backup & Restore Guide

This document covers backup strategies, procedures, and disaster recovery for Tenanto.


Table of Contents

  1. Overview
  2. What to Backup
  3. Database Backups
  4. File Storage Backups
  5. Configuration Backups
  6. Automated Backup Scripts
  7. Restore Procedures
  8. Disaster Recovery
  9. Testing Backups

Overview

Backup Strategy: 3-2-1 Rule

Follow the 3-2-1 backup rule:

Backup Types

Type Frequency Retention Use Case
Full Weekly 4 weeks Complete restoration
Incremental Daily 14 days Point-in-time recovery
Transaction logs Continuous 7 days Minute-level recovery

What to Backup

Critical Data

Component Location Priority
PostgreSQL Database DB server Critical
User uploads storage/app/public/ Critical
Environment config .env Critical
SSL certificates /etc/letsencrypt/ High
Queue failed jobs Redis / failed_jobs table Medium

Not Needed in Backup


Database Backups

Manual Backup

# Full database backup
pg_dump -U tenanto -h localhost -Fc tenanto > backup_$(date +%Y%m%d_%H%M%S).dump

# Plain SQL format (larger but readable)
pg_dump -U tenanto -h localhost tenanto > backup_$(date +%Y%m%d_%H%M%S).sql

# Compressed plain SQL
pg_dump -U tenanto -h localhost tenanto | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz

Schema Only (for development)

pg_dump -U tenanto -h localhost --schema-only tenanto > schema.sql

Data Only

pg_dump -U tenanto -h localhost --data-only tenanto > data.sql

Docker Environment

# From host machine
docker compose exec -T db pg_dump -U tenanto tenanto > backup.sql

# Compressed
docker compose exec -T db pg_dump -U tenanto tenanto | gzip > backup.sql.gz

Backup Specific Tables

# Backup only tenants and users (for migration)
pg_dump -U tenanto -h localhost -t tenants -t users tenanto > core_tables.sql

File Storage Backups

Local Storage

# Backup user uploads
tar -czvf uploads_$(date +%Y%m%d).tar.gz storage/app/public/

# Incremental backup with rsync
rsync -avz --delete storage/app/public/ /backup/files/

S3/Object Storage

If using S3, configure cross-region replication or use AWS Backup:

# Sync to backup bucket
aws s3 sync s3://tenanto-production s3://tenanto-backup --storage-class STANDARD_IA

Configuration Backups

Encrypt Sensitive Config

# Backup .env with encryption
gpg --symmetric --cipher-algo AES256 .env -o env_backup_$(date +%Y%m%d).gpg

# Decrypt
gpg -d env_backup_20250101.gpg > .env.restored

Version Control Configs

Keep deployment configs in version control (without secrets):


Automated Backup Scripts

Daily Backup Script

Create /opt/scripts/tenanto-backup.sh:

#!/bin/bash
set -e

# Configuration
BACKUP_DIR="/var/backups/tenanto"
RETENTION_DAYS=14
DB_NAME="tenanto"
DB_USER="tenanto"
APP_DIR="/var/www/tenanto"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/tenanto-backup.log"

# Ensure backup directory exists
mkdir -p "$BACKUP_DIR"/{database,files,config}

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "Starting backup..."

# 1. Database backup
log "Backing up database..."
pg_dump -U "$DB_USER" -Fc "$DB_NAME" > "$BACKUP_DIR/database/db_$TIMESTAMP.dump"
if [ $? -eq 0 ]; then
    log "Database backup completed: db_$TIMESTAMP.dump"
else
    log "ERROR: Database backup failed!"
    exit 1
fi

# 2. File storage backup
log "Backing up uploads..."
tar -czf "$BACKUP_DIR/files/uploads_$TIMESTAMP.tar.gz" \
    -C "$APP_DIR" storage/app/public 2>/dev/null || true
log "Files backup completed: uploads_$TIMESTAMP.tar.gz"

# 3. Configuration backup (encrypted)
log "Backing up configuration..."
if [ -f "$APP_DIR/.env" ]; then
    # Use a passphrase from environment or file
    echo "$BACKUP_ENCRYPTION_KEY" | gpg --batch --yes --passphrase-fd 0 \
        --symmetric --cipher-algo AES256 \
        "$APP_DIR/.env" -o "$BACKUP_DIR/config/env_$TIMESTAMP.gpg"
    log "Config backup completed: env_$TIMESTAMP.gpg"
fi

# 4. Cleanup old backups
log "Cleaning up old backups..."
find "$BACKUP_DIR/database" -name "*.dump" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_DIR/files" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_DIR/config" -name "*.gpg" -mtime +$RETENTION_DAYS -delete

# 5. Calculate backup sizes
DB_SIZE=$(du -sh "$BACKUP_DIR/database/db_$TIMESTAMP.dump" | cut -f1)
FILES_SIZE=$(du -sh "$BACKUP_DIR/files/uploads_$TIMESTAMP.tar.gz" 2>/dev/null | cut -f1 || echo "0")

log "Backup completed successfully!"
log "Database: $DB_SIZE | Files: $FILES_SIZE"

# Optional: Upload to offsite storage
# aws s3 cp "$BACKUP_DIR/database/db_$TIMESTAMP.dump" s3://tenanto-backups/database/
# aws s3 cp "$BACKUP_DIR/files/uploads_$TIMESTAMP.tar.gz" s3://tenanto-backups/files/

Cron Configuration

# Daily backup at 3 AM
0 3 * * * /opt/scripts/tenanto-backup.sh >> /var/log/tenanto-backup.log 2>&1

# Weekly full backup with longer retention (Sundays at 2 AM)
0 2 * * 0 /opt/scripts/tenanto-full-backup.sh >> /var/log/tenanto-backup.log 2>&1

Systemd Timer (Alternative to Cron)

/etc/systemd/system/tenanto-backup.timer:

[Unit]
Description=Daily Tenanto Backup

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

/etc/systemd/system/tenanto-backup.service:

[Unit]
Description=Tenanto Backup Service
After=postgresql.service

[Service]
Type=oneshot
ExecStart=/opt/scripts/tenanto-backup.sh
User=root
Environment=BACKUP_ENCRYPTION_KEY=your-secure-key

[Install]
WantedBy=multi-user.target

Enable:

sudo systemctl enable --now tenanto-backup.timer

Restore Procedures

Database Restore

Full Restore

# Stop application
php artisan down

# Drop and recreate database
sudo -u postgres psql -c "DROP DATABASE tenanto;"
sudo -u postgres psql -c "CREATE DATABASE tenanto OWNER tenanto;"

# Restore from custom format
pg_restore -U tenanto -d tenanto backup.dump

# Or from SQL format
psql -U tenanto -d tenanto < backup.sql

# Run any pending migrations
php artisan migrate --force

# Clear caches
php artisan cache:clear
php artisan config:cache
php artisan route:cache

# Bring application back up
php artisan up

Docker Environment Restore

# Stop containers
docker compose down

# Start only database
docker compose up -d db
sleep 5

# Restore database
cat backup.sql | docker compose exec -T db psql -U tenanto -d tenanto

# Start all services
docker compose up -d

Point-in-Time Recovery

For PostgreSQL with WAL archiving enabled:

# Restore base backup
pg_restore -U tenanto -d tenanto_restore base_backup.dump

# Apply WAL logs up to specific time
# (Requires PostgreSQL configured with archive_mode and recovery.conf)

File Storage Restore

# Extract uploads
tar -xzvf uploads_20250101.tar.gz -C /var/www/tenanto/

# Fix permissions
chown -R www-data:www-data /var/www/tenanto/storage
chmod -R 775 /var/www/tenanto/storage

# Recreate storage link if needed
php artisan storage:link

Configuration Restore

# Decrypt .env backup
gpg -d env_backup_20250101.gpg > .env

# Verify and update as needed (database credentials, etc.)
nano .env

# Regenerate caches
php artisan config:cache

Disaster Recovery

Recovery Time Objectives

Scenario RTO RPO
Database corruption 1 hour 24 hours (daily backup)
Server failure 2 hours 24 hours
Data center failure 4 hours 24 hours

Recovery Checklist

1. Assess Situation

2. Provision Infrastructure

3. Restore Data

4. Verify Application

5. Post-Recovery

Runbook: Complete Server Recovery

# 1. Provision new server with required software
sudo apt update && sudo apt install -y \
    php8.4-fpm php8.4-cli php8.4-pgsql php8.4-redis \
    php8.4-bcmath php8.4-intl php8.4-gd php8.4-xml \
    php8.4-curl php8.4-zip php8.4-mbstring \
    nginx postgresql-16 redis-server supervisor

# 2. Clone application (or copy from your source)
cd /var/www
git clone https://your-git-host.com/your-org/tenanto.git tenanto
cd tenanto

# 3. Install dependencies
composer install --optimize-autoloader --no-dev
npm ci && npm run build

# 4. Restore configuration
gpg -d /backup/config/env_latest.gpg > .env

# 5. Restore database
pg_restore -U tenanto -d tenanto /backup/database/db_latest.dump

# 6. Restore uploads
tar -xzvf /backup/files/uploads_latest.tar.gz -C /var/www/tenanto/

# 7. Fix permissions
chown -R www-data:www-data /var/www/tenanto
chmod -R 755 /var/www/tenanto
chmod -R 775 storage bootstrap/cache

# 8. Rebuild caches
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan storage:link

# 9. Restart services
sudo systemctl restart php8.4-fpm nginx
sudo supervisorctl restart tenanto-worker:*

# 10. Verify
curl -s http://localhost/health | jq

Testing Backups

Monthly Backup Verification

Critical: Untested backups are not backups!

# 1. Create test environment
docker compose -f docker-compose.test.yml up -d

# 2. Restore latest backup
docker compose -f docker-compose.test.yml exec db \
    pg_restore -U tenanto -d tenanto_test /backups/latest.dump

# 3. Run verification queries
docker compose -f docker-compose.test.yml exec db psql -U tenanto -d tenanto_test -c "
    SELECT COUNT(*) as tenants FROM tenants;
    SELECT COUNT(*) as users FROM users;
    SELECT COUNT(*) as projects FROM projects;
    SELECT COUNT(*) as tasks FROM tasks;
"

# 4. Test application
docker compose -f docker-compose.test.yml exec app php artisan migrate:status
docker compose -f docker-compose.test.yml exec app php artisan test --group=critical

# 5. Cleanup
docker compose -f docker-compose.test.yml down -v

Backup Integrity Checks

# Verify PostgreSQL backup integrity
pg_restore --list backup.dump > /dev/null && echo "Backup valid" || echo "Backup corrupted!"

# Verify tar archive
tar -tzf uploads.tar.gz > /dev/null && echo "Archive valid" || echo "Archive corrupted!"

# Verify GPG encrypted file
gpg --batch --decrypt --dry-run env_backup.gpg 2>/dev/null && echo "Encrypted file valid"

Monitoring Backup Health

Alert on Backup Failures

Add to your monitoring system:

# Example: Prometheus alert rule
- alert: BackupMissing
  expr: time() - backup_last_success_timestamp > 86400 * 2
  for: 1h
  labels:
    severity: critical
  annotations:
    summary: "Tenanto backup missing for more than 2 days"

Backup Report Script

#!/bin/bash
# Send daily backup status report

BACKUP_DIR="/var/backups/tenanto"

echo "=== Tenanto Backup Report $(date) ==="
echo ""
echo "Database backups:"
ls -lh "$BACKUP_DIR/database/" | tail -5
echo ""
echo "Total database backup size: $(du -sh "$BACKUP_DIR/database/" | cut -f1)"
echo ""
echo "File backups:"
ls -lh "$BACKUP_DIR/files/" | tail -5
echo ""
echo "Total file backup size: $(du -sh "$BACKUP_DIR/files/" | cut -f1)"

Related Documentation


Support

For backup and recovery assistance, contact the Tenanto operations team.