Regular backups are essential for protecting your SnailyCAD data. This guide covers how to backup and restore your database and uploaded files.
Never skip backups before updates or major changes. Data loss without backups is unrecoverable.
What to Backup
SnailyCAD stores data in two locations:
- PostgreSQL Database - All CAD data (users, records, calls, etc.)
- File Uploads - Images and documents in
apps/api/public
Database Structure
The database uses Prisma ORM with PostgreSQL (apps/api/prisma/schema.prisma:4-6). Key tables include:
cad - CAD configuration and settings
User - User accounts and authentication
Citizen - Citizen profiles and data
- Records, calls, vehicles, and other operational data
File Storage
Uploaded files are stored in the API public directory (production.docker-compose.yml:34):
- Docker:
./apps/api/public (mounted as volume)
- Standalone:
apps/api/public
Database Backup
Docker Installation
Create Database Backup
# Create backup directory
mkdir -p backups
# Backup with timestamp
docker exec snaily-cad-postgres pg_dump -U postgres snaily-cad-v4 > backups/snailycad-$(date +%Y%m%d-%H%M%S).sql
Backup with Compression
# Compressed backup (saves disk space)
docker exec snaily-cad-postgres pg_dump -U postgres snaily-cad-v4 | gzip > backups/snailycad-$(date +%Y%m%d-%H%M%S).sql.gz
# Custom format (faster restore, supports parallel restore)
docker exec snaily-cad-postgres pg_dump -U postgres -Fc snaily-cad-v4 > backups/snailycad-$(date +%Y%m%d-%H%M%S).dump
Standalone Installation
# Create backup directory
mkdir -p backups
# Standard backup
pg_dump -U postgres -h localhost -p 5432 snaily-cad-v4 > backups/snailycad-$(date +%Y%m%d-%H%M%S).sql
# Compressed backup
pg_dump -U postgres -h localhost -p 5432 snaily-cad-v4 | gzip > backups/snailycad-$(date +%Y%m%d-%H%M%S).sql.gz
Replace postgres, localhost, 5432, and snaily-cad-v4 with your actual database credentials from .env.example:6-30.
Backup PostgreSQL Data Directory
For complete backups including all databases:
# Docker - backup the entire data volume (production.docker-compose.yml:15)
tar -czf backups/postgres-data-$(date +%Y%m%d-%H%M%S).tar.gz ./.data
# Standalone - backup PostgreSQL data directory
sudo tar -czf backups/postgres-data-$(date +%Y%m%d-%H%M%S).tar.gz /var/lib/postgresql/data
Stop the database before backing up the data directory to avoid corruption:docker compose -f production.docker-compose.yml stop postgres
File Uploads Backup
Backup Public Directory
# Create backup directory
mkdir -p backups
# Backup all uploaded files
tar -czf backups/uploads-$(date +%Y%m%d-%H%M%S).tar.gz apps/api/public
Backup Specific Directories
# Backup only images
tar -czf backups/images-$(date +%Y%m%d-%H%M%S).tar.gz apps/api/public/images
# Backup only citizen images
tar -czf backups/citizens-$(date +%Y%m%d-%H%M%S).tar.gz apps/api/public/citizens
Sync to Remote Storage
# Using rsync to remote server
rsync -avz apps/api/public/ user@backup-server:/backups/snailycad/public/
# Using rclone to cloud storage
rclone sync apps/api/public/ remote:snailycad-backups/public/
Complete Backup Script
Create a comprehensive backup script:
#!/bin/bash
set -e
# Configuration
BACKUP_DIR="/backups/snailycad"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
RETENTION_DAYS=30
# Create backup directory
mkdir -p "$BACKUP_DIR"
echo "Starting SnailyCAD backup at $TIMESTAMP"
# Backup database (Docker)
echo "Backing up database..."
docker exec snaily-cad-postgres pg_dump -U postgres -Fc snaily-cad-v4 > "$BACKUP_DIR/db-$TIMESTAMP.dump"
# Backup uploaded files
echo "Backing up uploaded files..."
tar -czf "$BACKUP_DIR/uploads-$TIMESTAMP.tar.gz" apps/api/public
# Backup configuration
echo "Backing up configuration..."
cp .env "$BACKUP_DIR/env-$TIMESTAMP"
cp production.docker-compose.yml "$BACKUP_DIR/docker-compose-$TIMESTAMP.yml"
# Remove old backups
echo "Removing backups older than $RETENTION_DAYS days..."
find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete
echo "Backup completed successfully!"
# Show backup size
du -sh "$BACKUP_DIR"
Make it executable:
chmod +x backup.sh
./backup.sh
Database Restore
Docker Installation
Restore from SQL Backup
# Stop the API to prevent conflicts
docker compose -f production.docker-compose.yml stop api client
# Drop existing database (WARNING: destroys current data)
docker exec snaily-cad-postgres psql -U postgres -c "DROP DATABASE IF EXISTS \"snaily-cad-v4\";"
# Create fresh database
docker exec snaily-cad-postgres psql -U postgres -c "CREATE DATABASE \"snaily-cad-v4\";"
# Restore from backup
cat backups/snailycad-20240103-120000.sql | docker exec -i snaily-cad-postgres psql -U postgres -d snaily-cad-v4
# Restore from compressed backup
zcat backups/snailycad-20240103-120000.sql.gz | docker exec -i snaily-cad-postgres psql -U postgres -d snaily-cad-v4
# Start services
docker compose -f production.docker-compose.yml start api client
# Stop services
docker compose -f production.docker-compose.yml stop api client
# Restore using pg_restore
docker exec -i snaily-cad-postgres pg_restore -U postgres -d snaily-cad-v4 --clean --if-exists < backups/snailycad-20240103-120000.dump
# Start services
docker compose -f production.docker-compose.yml start api client
Standalone Installation
# Stop services
pm2 stop snailycad-api snailycad-client
# or
systemctl stop snailycad-api snailycad-client
# Drop and recreate database
psql -U postgres -c "DROP DATABASE IF EXISTS \"snaily-cad-v4\";"
psql -U postgres -c "CREATE DATABASE \"snaily-cad-v4\";"
# Restore from backup
psql -U postgres -d snaily-cad-v4 < backups/snailycad-20240103-120000.sql
# Or from compressed
zcat backups/snailycad-20240103-120000.sql.gz | psql -U postgres -d snaily-cad-v4
# Start services
pm2 start snailycad-api snailycad-client
# or
systemctl start snailycad-api snailycad-client
Restore PostgreSQL Data Directory
# Stop database
docker compose -f production.docker-compose.yml stop postgres
# Backup current data (safety measure)
mv ./.data ./.data.old
# Extract backup
tar -xzf backups/postgres-data-20240103-120000.tar.gz
# Start database
docker compose -f production.docker-compose.yml start postgres
# If successful, remove old data
rm -rf ./.data.old
File Uploads Restore
Restore Public Directory
# Backup current files (safety measure)
mv apps/api/public apps/api/public.old
# Extract backup
tar -xzf backups/uploads-20240103-120000.tar.gz
# Verify permissions
chown -R 1000:1000 apps/api/public # Docker user
# or
chown -R www-data:www-data apps/api/public # Standalone
# If successful, remove old files
rm -rf apps/api/public.old
Restore from Remote Storage
# Using rsync
rsync -avz user@backup-server:/backups/snailycad/public/ apps/api/public/
# Using rclone
rclone sync remote:snailycad-backups/public/ apps/api/public/
Complete Restore Script
#!/bin/bash
set -e
# Configuration
BACKUP_FILE=$1
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: ./restore.sh <backup-timestamp>"
echo "Example: ./restore.sh 20240103-120000"
exit 1
fi
BACKUP_DIR="/backups/snailycad"
echo "Restoring SnailyCAD from backup $BACKUP_FILE"
# Stop services
echo "Stopping services..."
docker compose -f production.docker-compose.yml stop api client
# Restore database
echo "Restoring database..."
docker exec -i snaily-cad-postgres pg_restore -U postgres -d snaily-cad-v4 --clean --if-exists < "$BACKUP_DIR/db-$BACKUP_FILE.dump"
# Restore uploads
echo "Restoring uploaded files..."
mv apps/api/public apps/api/public.old
tar -xzf "$BACKUP_DIR/uploads-$BACKUP_FILE.tar.gz"
# Start services
echo "Starting services..."
docker compose -f production.docker-compose.yml start api client
echo "Restore completed successfully!"
echo "Old files backed up to apps/api/public.old"
Automated Backups
Cron Job Setup
Schedule automatic backups using cron:
# Edit crontab
crontab -e
# Add daily backup at 2 AM
0 2 * * * /path/to/snailycad/backup.sh >> /var/log/snailycad-backup.log 2>&1
# Add hourly database backup
0 * * * * docker exec snaily-cad-postgres pg_dump -U postgres -Fc snaily-cad-v4 > /backups/snailycad/hourly-$(date +\%H).dump
Systemd Timer (Alternative to Cron)
Create /etc/systemd/system/snailycad-backup.service:
[Unit]
Description=SnailyCAD Backup
[Service]
Type=oneshot
ExecStart=/path/to/snailycad/backup.sh
User=your-user
Create /etc/systemd/system/snailycad-backup.timer:
[Unit]
Description=SnailyCAD Backup Timer
[Timer]
OnCalendar=daily
OnCalendar=02:00
Persistent=true
[Install]
WantedBy=timers.target
Enable the timer:
sudo systemctl enable snailycad-backup.timer
sudo systemctl start snailycad-backup.timer
Backup Best Practices
- Backup before updates - Always backup before updating SnailyCAD
- Test restores regularly - Verify backups work by testing restore procedures
- Store offsite - Keep backups on a different server or cloud storage
- Encrypt sensitive backups - Use GPG or similar for encryption
- Monitor backup jobs - Set up alerts for failed backups
- Document restore procedures - Keep instructions accessible
- Retain multiple versions - Keep daily, weekly, and monthly backups
- Automate backups - Use cron or systemd timers for consistency
Backup Storage Recommendations
Retention Strategy
- Hourly: Last 24 hours
- Daily: Last 30 days
- Weekly: Last 12 weeks
- Monthly: Last 12 months
Storage Requirements
Estimate backup sizes:
- Database: Varies by CAD size (typically 100MB - 2GB)
- Uploads: Depends on image quantity (typically 500MB - 5GB)
- Total: Plan for 5-10x daily backup size for retention
Remote Backup Options
- Cloud Storage: AWS S3, Google Cloud Storage, Backblaze B2
- Remote Server: Dedicated backup server via rsync
- Network Storage: NAS or SAN devices
- Managed Backup: Commercial backup services
Disaster Recovery
Recovery Time Objective (RTO)
Typical restore times:
- Database only: 5-15 minutes
- Database + Files: 15-30 minutes
- Complete system: 30-60 minutes
Recovery Point Objective (RPO)
Minimize data loss:
- Hourly backups: Max 1 hour data loss
- Daily backups: Max 24 hours data loss
- Continuous replication: Near-zero data loss
Next Steps