What's automatic
- Every night at 03:00 server time, the cron runs
VACUUM INTO /app/data/backups/maillayer-YYYY-MM-DD.db. - Old backups are pruned after
BACKUP_KEEP_N(default 14) snapshots. - VACUUM INTO is online — doesn't lock the live DB.
Where they live
/app/data/backups/ inside the container. Since /app/data is a mounted volume, backups survive container rebuilds.
Restoring
# Stop the container
docker compose -f /opt/maillayer/docker-compose.yml down
# Replace the live DB with a backup
cp /var/lib/docker/volumes/maillayer_maillayer-data/_data/backups/maillayer-2026-04-29.db \
/var/lib/docker/volumes/maillayer_maillayer-data/_data/maillayer.db
# Start back up
docker compose -f /opt/maillayer/docker-compose.yml up -dOff-volume backup
The included backups are inside the same volume — fine for restart-class recovery, useless if the volume itself dies. Ship them off:
# Cron: rsync backups + .env to S3 every morning
0 4 * * * rsync -av /opt/maillayer/.env \
/var/lib/docker/volumes/maillayer_maillayer-data/_data/backups/ \
s3://my-bucket/maillayer-backups/Don't forget AUTH_SECRET
Your backup is useless without the same
AUTH_SECRET that encrypted the credentials. Either: (a) keep AUTH_SECRET in your platform's env vars (recommended), or (b) include /opt/maillayer/.env in your off-box backup.Manual backup right now
docker exec -it maillayer sh -c \
'sqlite3 /app/data/maillayer.db ".backup /app/data/backups/manual-$(date +%F-%H%M).db"'