Backup & restore

There are exactly two things you need to back up: the database and the storage volume.

What to back up

WhatDefault locationRestorability
SQLite DB/data/app.db (Docker)
/var/lib/speedyfiles/app.db (bare-metal)
Required — contains users, packages, tokens, settings, audit
Storage volume/srv/filesRequired for local backend — contains bulk file contents
.env file./ or /opt/speedyfiles/Needed because SESSION_SECRET decrypts stored credentials

NOT needed

Hot backup (no downtime)

SQLite's .backup command is safe to run while the app is using the DB:

BACKUP_DIR=/srv/backups/speedyfiles
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d-%H%M%S)

# 1. SQLite snapshot
sqlite3 /data/app.db ".backup $BACKUP_DIR/app-$DATE.db"

# 2. Files volume (rsync incremental)
rsync -a --delete /srv/files/ "$BACKUP_DIR/files/"

# 3. Bundle for offsite
tar czf "$BACKUP_DIR/sf-$DATE.tar.gz" \
    "$BACKUP_DIR/app-$DATE.db" "$BACKUP_DIR/files/" \
    /opt/speedyfiles/.env
# ship to S3 / B2 / wherever:
aws s3 cp "$BACKUP_DIR/sf-$DATE.tar.gz" s3://your-backup-bucket/

Run from cron daily:

# /etc/cron.d/speedyfiles-backup
30 2 * * * root /usr/local/sbin/sf-backup.sh

Cold backup (with downtime, simpler)

systemctl stop speedyfiles  # or: docker compose stop
tar czf sf-cold-$(date +%Y%m%d).tar.gz \
    /data /srv/files /opt/speedyfiles/.env
systemctl start speedyfiles  # or: docker compose start

S3 storage backend

If STORAGE_BACKEND=s3, the files are in your S3 bucket — back up the bucket separately using S3 versioning + replication. Only the SQLite DB and .env need to be backed up server-side.

Restore

systemctl stop speedyfiles

# Restore DB
cp /path/to/backup/app-20260101.db /data/app.db
chown speedyfiles:speedyfiles /data/app.db

# Restore files
rsync -a /path/to/backup/files/ /srv/files/
chown -R speedyfiles:speedyfiles /srv/files

# Restore .env (only if SESSION_SECRET was rotated since the backup)
cp /path/to/backup/.env /opt/speedyfiles/.env

systemctl start speedyfiles

Verifying a backup

Restore to a temp instance periodically. The cheapest test:

# Mount the backup on a test container
docker run --rm -it \
  -v /path/to/backup/app-20260101.db:/data/app.db \
  -v /path/to/backup/files:/srv/files \
  -e SESSION_SECRET=$(cat /path/to/backup/session_secret.txt) \
  -e PUBLIC_BASE_URL=http://localhost:5300 \
  -p 5300:5300 \
  ghcr.io/speedyfiles/speedyfiles:latest

# Visit http://localhost:5300, log in, browse packages