Skip to content

Database Backup

Velist includes an automatic database backup system with WAL (Write-Ahead Logging) support. Backups are created as single SQLite files that can be easily restored or uploaded to S3-compatible storage.

How It Works

Automatic Backup (Default)

Backups are triggered automatically based on the configured interval:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Bun Cron  │────►│   Backup    │────►│   Local     │
│  (Interval) │     │   Service   │     │   Storage   │
└─────────────┘     └──────┬──────┘     └─────────────┘

                           ├────────────────────────────►
                           │                      ┌─────────────┐
                           │                      │     S3      │
                           │                      │  (Optional) │
                           │                      └─────────────┘

Default schedule: Every 10 minutes
Retention: Keep last 10 backups (configurable)

Backup Process

When backup runs (automatic or manual):

  1. WAL Checkpoint - Merge WAL data into main database

    sql
    PRAGMA wal_checkpoint(TRUNCATE)

    Very fast (milliseconds). Brief lock, business continues immediately after.

  2. Copy Database - Copy dev.sqlite to backup folder 📁 File copy operation, doesn't affect running database

  3. Convert to Single-File - Remove WAL mode from backup copy

    sql
    PRAGMA journal_mode=DELETE

    ⚠️ Note: We skip VACUUM to avoid long operations. The backup is consistent without it.

  4. Upload to S3 (optional) - Upload backup file to S3 ☁️ Background operation, doesn't affect database

  5. Cleanup - Delete old backups beyond retention limit

Zero Downtime

Backup uses WAL checkpoint which only briefly locks the database (milliseconds). Your app continues serving users without interruption.

File Structure

With WAL mode enabled (running database):

db/
├── dev.sqlite        # Main database
├── dev.sqlite-wal    # Write-ahead log
└── dev.sqlite-shm    # Shared memory

Backup (single file, no WAL):

storage/backups/
├── backup-2024-01-15T10-30-00-000Z.sqlite
├── backup-2024-01-15T10-20-00-000Z.sqlite
└── backup-2024-01-15T10-10-00-000Z.sqlite

Configuration

Add to your .env file:

bash
# Enable/disable auto-backup
BACKUP_ENABLED=true

# Backup interval (minutes)
BACKUP_INTERVAL_MINUTES=10

# Number of backups to keep locally
BACKUP_RETENTION_COUNT=10

# Local backup path
BACKUP_LOCAL_PATH=./storage/backups

# S3 upload (optional)
BACKUP_S3_ENABLED=false
BACKUP_S3_PATH=backups/database

Backup Triggers

1. Automatic Backup (Default)

Backups run automatically as a silent background process:

When it starts:

  • Immediately when server starts (if BACKUP_ENABLED=true)
  • Then every X minutes based on interval

Example with 10-minute interval:

Server Start

[Backup] Auto-backup started. Interval: 10 minutes

10:00 - First backup
10:10 - Auto backup
10:20 - Auto backup
10:30 - Auto backup
...

No user notifications - Backup is completely silent. Check logs or management UI to verify.

2. Manual Backup

Trigger backup anytime via UI or API:

bash
# Via API
curl -X POST /backup/now

Or click "Backup Now" button in the backup management UI at /backup.

3. Silent Background Process

Backup runs silently in the background. No notifications are sent to users.

Check server logs to see backup status:

[Backup] Auto-backup started. Interval: 10 minutes
[Backup] Completed: backup-2024-01-15T10-30-00-000Z.sqlite (2.5 MB)
[Backup] Uploaded to S3: backups/database/backup-2024-01-15T10-30-00-000Z.sqlite

Management UI

Access the backup dashboard at /backup:

Features:

  • Stats Cards - DB size, backup count, auto-backup status, S3 status
  • Backup Now - Trigger manual backup
  • Configuration - Adjust interval, retention, S3 settings
  • Backup History - List, download, delete backups
  • Download - Get backup file directly

Screenshots:

┌─────────────────────────────────────────┐
│  Database Backup                        │
│  Auto-backup with WAL checkpoint        │
├─────────────────────────────────────────┤
│  DB Size: 15 MB    │  Local: 10         │
│  Auto: Every 10m   │  S3: Disabled      │
├─────────────────────────────────────────┤
│  [Backup Now]  [Configure]              │
├─────────────────────────────────────────┤
│  Backup History                         │
│  ┌─────────────────────────────────┐   │
│  │ 🗂️ backup-2024-01-15... 2.5 MB │   │
│  │    2.5 MB • Jan 15, 10:30 AM   │   │
│  │    [⬇️] [🗑️]                    │   │
│  └─────────────────────────────────┘   │
└─────────────────────────────────────────┘

Restoring from Backup

Manual Restore (Current Method)

⚠️ Warning: This replaces your current database. Make sure to backup first!

bash
# 1. Stop the server
# 2. Backup current database (just in case)
cp db/dev.sqlite db/dev.sqlite.backup.$(date +%s)

# 3. Copy backup file
cp storage/backups/backup-2024-01-15T10-30-00-000Z.sqlite db/dev.sqlite

# 4. Start server
bun run dev

Programmatic Restore (Coming Soon)

typescript
// Planned feature
await backupService.restore('backup-2024-01-15T10-30-00-000Z.sqlite')

S3 Upload

Enable S3 backup for offsite storage:

bash
# .env
BACKUP_S3_ENABLED=true
BACKUP_S3_PATH=backups/database

# Storage config (existing)
STORAGE_DRIVER=s3
S3_BUCKET=my-backups
S3_REGION=us-east-1
S3_ENDPOINT=https://s3.wasabisys.com
S3_ACCESS_KEY=xxx
S3_SECRET_KEY=xxx

Backups will be uploaded to:

s3://my-backups/backups/database/backup-2024-01-15T10-30-00-000Z.sqlite

Best Practices

For Development:

bash
# Default settings are fine
BACKUP_ENABLED=true
BACKUP_INTERVAL_MINUTES=10
BACKUP_RETENTION_COUNT=10

For Production:

bash
# Less frequent, more retention, S3 enabled
BACKUP_ENABLED=true
BACKUP_INTERVAL_MINUTES=60          # Every hour
BACKUP_RETENTION_COUNT=48           # Keep 2 days locally
BACKUP_S3_ENABLED=true              # Upload to S3
BACKUP_S3_PATH=backups/production   # Organized path

For Critical Data:

bash
# Frequent backups, long retention
BACKUP_ENABLED=true
BACKUP_INTERVAL_MINUTES=5           # Every 5 minutes
BACKUP_RETENTION_COUNT=144          # Keep 12 hours locally
BACKUP_S3_ENABLED=true              # S3 for disaster recovery

Production Considerations

Does backup affect performance?

  • WAL checkpoint: Very brief lock (milliseconds), users won't notice
  • File copy: Depends on DB size (100MB ~ 1 second, 1GB ~ 10 seconds)
  • S3 upload: Async, doesn't block

Recommendation for large databases (>1GB):

  • Increase interval: BACKUP_INTERVAL_MINUTES=60 (hourly)
  • Or use external backup tools for very large DBs

Troubleshooting

Backup not running?

Check if enabled:

bash
grep BACKUP_ENABLED .env

Check server logs:

[Backup] Auto-backup started. Interval: 10 minutes
[Backup] Success: backup-2024-01-15T10-30-00-000Z.sqlite (2.5 MB)

S3 upload failing?

Check storage configuration:

bash
grep STORAGE_DRIVER .env
grep S3_ .env

Test storage:

typescript
import { createStorage } from '$features/_core/storage'

const storage = createStorage()
const url = storage.getPublicUrl('test.txt')
console.log(url)

WAL files not merging?

Check WAL mode is enabled:

sql
PRAGMA journal_mode;
-- Should return: wal

Force checkpoint manually:

sql
PRAGMA wal_checkpoint(TRUNCATE);

Backup causing performance issues?

Symptoms: App slow when backup runs

Solutions:

  1. Increase interval: Don't backup too frequently

    bash
    BACKUP_INTERVAL_MINUTES=60  # Hourly instead of every 10 min
  2. Check database size:

    bash
    ls -lh db/dev.sqlite

    If >1GB, consider external backup solutions

  3. Monitor checkpoint duration:

    sql
    PRAGMA wal_checkpoint(TRUNCATE);
    -- Should complete in < 1 second
  4. Disable S3 if not needed: S3 upload can be slow for large files

    bash
    BACKUP_S3_ENABLED=false

API Reference

Routes

MethodPathDescription
GET/backupBackup management UI
POST/backup/nowTrigger manual backup
POST/backup/configUpdate configuration
DELETE/backup/:filenameDelete backup

Service Methods

typescript
import { backupService } from '$features/backup/service'

// Start auto-backup
backupService.start()

// Stop auto-backup
backupService.stop()

// Manual backup
const result = await backupService.performBackup(userId)
// { success: true, filename: '...', size: 2500000, s3Uploaded: true }

// Get backups
const backups = await backupService.getBackups()

// Update config
backupService.updateConfig({
  intervalMinutes: 30,
  s3Enabled: true
})

See Also

  • Storage - S3/local storage configuration
  • Database - SQLite & WAL mode details

Released under the MIT License.