Back up Docker app SQLite databases without stopping containers

Most self-hosted media apps (Plex, Jellyfin, Sonarr, Radarr, Lidarr, Prowlarr, Bazarr, Tautulli, Overseerr/Jellyseerr, SABnzbd, Calibre and others) keep their state in SQLite files under their config volume. Copying those files with cp or tar while the app is running can give you a corrupt copy. SQLite's online backup API makes a consistent copy while the app keeps running. 
 The core idea 
 sqlite3 -readonly /data/sonarr/config/sonarr.db ".timeout 60000" ".backup '/tmp/sonarr.db'"
sqlite3 /tmp/sonarr.db "PRAGMA quick_check;" # should print: ok
gzip -c /tmp/sonarr.db > /mnt/backup/$(hostname -s)/sonarr/$(date +%Y.%m.%d_%H).sonarr.db.gz
 
 .timeout makes sqlite wait for the app's write locks instead of failing. 
 A script around it 
 Keep a simple list of app|path|check entries at the top, for example: 
 DBS=(
 "plex|$DATA/plex/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db|0"
 "sonarr|$DATA/sonarr/config/sonarr.db|1"
 "radarr|$DATA/radarr/config/radarr.db|1"
)
 
 The last field turns the integrity check on or off. Plex's database uses custom tokenizers that make quick_check report false errors, so set it to 0 for Plex. 
 Good habits worth building in: 
 
 
 Refuse to run if the backup share isn't mounted: mountpoint -q /mnt/backup || exit 1 . Otherwise backups fill the local disk. 
 
 Check free space before starting (for example, require 10 GB). 
 
 Run gently with nice -n 19 ionice -c3 . 
 
 Prevent overlapping runs with flock -n . 
 
 Write to file.part , then mv , so a half-written backup never looks complete. 
 
 Also copy the app's own backup zips (the *arr apps write them to config/Backups ). 
 
 Add a --list option that shows what would be backed up and writes nothing. 
 
 Keep N days and delete older copies: find ... -name '*.gz' -mtime +14 -delete . 
 
 Schedule 
 30 1 * * * root /usr/local/bin/backup_docker_dbs.sh >/dev/null 2>&1
 
 Restore 
 
 
 docker stop <container> 
 
 Move the live app.db aside, and remove app.db-wal and app.db-shm . 
 
 gunzip -c <backup>.gz > app.db 
 
 
 chown <puid>:<pgid> app.db (the IDs the container runs as). 
 
 docker start <container>