From 8ffe3252a2c2dddba6de8e0a7dc0830e0fab0b2f Mon Sep 17 00:00:00 2001 From: Administrator Date: Sat, 14 Feb 2026 03:54:37 +0000 Subject: [PATCH] docs: update immich_backup --- immich_backup.md | 184 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 133 insertions(+), 51 deletions(-) diff --git a/immich_backup.md b/immich_backup.md index df67301..ccb8ad6 100644 --- a/immich_backup.md +++ b/immich_backup.md @@ -2,7 +2,7 @@ title: Immich Backup and Restore description: Immich backup with Kopia published: true -date: 2026-02-14T03:34:42.017Z +date: 2026-02-14T03:54:27.661Z tags: editor: markdown dateCreated: 2026-02-14T03:14:32.594Z @@ -108,11 +108,21 @@ Immich's official backup approach uses `pg_dump` for the database: - Produces compressed `.sql.gz` files - Database remains available during backup +For the photo/video library, we use a **hybrid approach**: +- **Database**: Backed up locally as `dump.sql.gz` for fast component-level restore +- **Library**: Backed up directly by Kopia (no tar) for optimal deduplication and incremental backups + +**Why not tar the library?** +- Kopia deduplicates at the file level - adding 1 photo shouldn't require backing up the entire library again +- Individual file access for selective restore +- Better compression and faster incremental backups +- Lower risk - corrupted tar loses everything, corrupted file only affects that file + **Key Features:** - No downtime required - Consistent point-in-time snapshot - Standard PostgreSQL format (portable across systems) -- Can be restored to any PostgreSQL instance +- Efficient incremental backups of photo library ## Setting Up Immich Backups @@ -150,17 +160,23 @@ docker exec -t immich_postgres pg_dump \ --username=postgres \ | gzip > "/opt/immich-backups/dump.sql.gz" -# Backup library (photos/videos) -tar -czf /opt/immich-backups/library.tar.gz -C /srv/immich library - # Backup configuration files cp docker-compose.yml /opt/immich-backups/ cp .env /opt/immich-backups/ + +# Backup library with Kopia (no tar - better deduplication) +kopia snapshot create /srv/immich/library \ + --tags immich,library,photos \ + --description "Immich library manual backup" ``` **What gets created:** -- Backup directory: `/opt/immich-backups/immich-YYYY-MM-DD-HH-MM-SS/` -- Contains: `dump.sql.gz` (database), `library.tar.gz` (photos), config files +- Local backup directory: `/opt/immich-backups/immich-YYYY-MM-DD-HH-MM-SS/` + - Contains: `dump.sql.gz` (database), config files +- Kopia snapshots: + - `/opt/immich-backups` (database + config) + - `/srv/immich/library` (photos/videos, no tar) + - `/opt/immich` (installation directory) #### Step 3: Automated Backup Script @@ -213,18 +229,16 @@ fi echo "[${BACKUP_DATE}] Immich database backup completed successfully" | tee -a "$LOG_FILE" -# Step 2: Backup upload library (photos/videos) -echo "[${BACKUP_DATE}] Backing up upload library..." | tee -a "$LOG_FILE" +# Step 2: Verify library location exists (Kopia will backup directly, no tar needed) +echo "[${BACKUP_DATE}] Verifying library location..." | tee -a "$LOG_FILE" # Get the upload location from docker-compose volumes UPLOAD_LOCATION="/srv/immich/library" if [ -d "${UPLOAD_LOCATION}" ]; then - tar -czf "${BACKUP_DIR}/immich-${BACKUP_DATE}/library.tar.gz" \ - -C "$(dirname ${UPLOAD_LOCATION})" \ - "$(basename ${UPLOAD_LOCATION})" 2>&1 | tee -a "$LOG_FILE" - - echo "[${BACKUP_DATE}] Upload library backup completed" | tee -a "$LOG_FILE" + LIBRARY_SIZE=$(du -sh ${UPLOAD_LOCATION} | cut -f1) + echo "[${BACKUP_DATE}] Library location verified: ${UPLOAD_LOCATION} (${LIBRARY_SIZE})" | tee -a "$LOG_FILE" + echo "[${BACKUP_DATE}] Kopia will backup library files directly (no tar, better deduplication)" | tee -a "$LOG_FILE" else echo "[${BACKUP_DATE}] WARNING: Upload location not found at ${UPLOAD_LOCATION}" | tee -a "$LOG_FILE" fi @@ -262,7 +276,25 @@ fi echo "[${BACKUP_DATE}] Kopia snapshot completed successfully" | tee -a "$LOG_FILE" -# Step 6: Also backup the Immich installation directory (configs, compose files) +# Step 6: Backup the library directly with Kopia (better deduplication than tar) +echo "[${BACKUP_DATE}] Creating Kopia snapshot of library..." | tee -a "$LOG_FILE" + +if [ -d "${UPLOAD_LOCATION}" ]; then + kopia snapshot create "${UPLOAD_LOCATION}" \ + --tags immich,library,photos \ + --description "Immich library ${BACKUP_DATE}" \ + 2>&1 | tee -a "$LOG_FILE" + + KOPIA_LIB_EXIT=${PIPESTATUS[0]} + + if [ $KOPIA_LIB_EXIT -ne 0 ]; then + echo "[${BACKUP_DATE}] WARNING: Kopia library snapshot failed" | tee -a "$LOG_FILE" + else + echo "[${BACKUP_DATE}] Library snapshot completed successfully" | tee -a "$LOG_FILE" + fi +fi + +# Step 7: Also backup the Immich installation directory (configs, compose files) echo "[${BACKUP_DATE}] Backing up Immich installation directory..." | tee -a "$LOG_FILE" kopia snapshot create "${IMMICH_DIR}" \ @@ -322,15 +354,19 @@ docker compose down # List available backups ls -lh /opt/immich-backups/ -# Choose a backup +# Choose a database backup BACKUP_PATH="/opt/immich-backups/immich-YYYYMMDD_HHMMSS" # Restore database gunzip < ${BACKUP_PATH}/dump.sql.gz | \ docker compose exec -T database psql --username=postgres --dbname=immich -# Restore library -tar -xzf ${BACKUP_PATH}/library.tar.gz -C /srv/immich/ +# Restore library from Kopia +kopia snapshot list --tags library +kopia restore /srv/immich/library + +# Fix permissions +chown -R 1000:1000 /srv/immich/library # Restore configuration (review changes first) cp ${BACKUP_PATH}/.env .env.restored @@ -376,19 +412,17 @@ cd /opt/immich # Stop Immich docker compose down -# Restore library from backup -BACKUP_PATH="/opt/immich-backups/immich-YYYYMMDD_HHMMSS" -tar -xzf ${BACKUP_PATH}/library.tar.gz -C /srv/immich/ +# Restore library from Kopia +kopia snapshot list --tags library +kopia restore /srv/immich/library + +# Fix permissions +chown -R 1000:1000 /srv/immich/library # Start Immich docker compose up -d ``` -**Note**: For library restore, ensure proper permissions after extraction: -```bash -chown -R 1000:1000 /srv/immich/library -``` - ### Method 2: Complete Server Rebuild (Kopia Restore) Use this when recovering to a completely new server or when local backups are unavailable. @@ -477,8 +511,9 @@ sleep 30 gunzip < ${LATEST_BACKUP}/dump.sql.gz | \ docker compose exec -T database psql --username=postgres --dbname=immich -# Restore library -tar -xzf ${LATEST_BACKUP}/library.tar.gz -C /srv/immich/ +# Restore library from Kopia +kopia snapshot list --tags library +kopia restore /srv/immich/library # Fix permissions chown -R 1000:1000 /srv/immich/library @@ -519,35 +554,52 @@ ls -lah /srv/immich/library/ To restore a single user's library without affecting others: +**Option A: Using Kopia Mount (Recommended)** + ```bash -cd /opt/immich - -# Stop Immich temporarily -docker compose down - -# Extract library to temporary location -BACKUP_PATH="/opt/immich-backups/immich-YYYYMMDD_HHMMSS" -mkdir -p /tmp/immich-restore -tar -xzf ${BACKUP_PATH}/library.tar.gz -C /tmp/immich-restore +# Mount the Kopia snapshot +kopia snapshot list --tags library +mkdir -p /mnt/kopia-library +kopia mount /mnt/kopia-library & # Find the user's directory (using user ID from database) # User libraries are typically in: library/{user-uuid}/ USER_UUID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Copy user's data back -rsync -av /tmp/immich-restore/library/${USER_UUID}/ \ +rsync -av /mnt/kopia-library/${USER_UUID}/ \ /srv/immich/library/${USER_UUID}/ # Fix permissions chown -R 1000:1000 /srv/immich/library/${USER_UUID}/ -# Cleanup -rm -rf /tmp/immich-restore +# Unmount +kopia unmount /mnt/kopia-library + +# Restart Immich to recognize changes +cd /opt/immich +docker compose restart immich-server +``` + +**Option B: Selective Kopia Restore** + +```bash +cd /opt/immich +docker compose down + +# Restore just the specific user's directory +kopia snapshot list --tags library +USER_UUID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + +# Restore with path filter +kopia restore /srv/immich/library \ + --snapshot-path="${USER_UUID}" + +# Fix permissions +chown -R 1000:1000 /srv/immich/library/${USER_UUID}/ # Start Immich docker compose up -d - -# Trigger re-scan for this user via the web UI ``` ### Scenario 3: Database Recovery Only @@ -726,18 +778,48 @@ When disaster strikes, follow this checklist: ### Storage Efficiency Using this two-tier approach: -- **Local**: Immich creates ~7 days of native backups (may be large, but short retention) -- **Offsite**: Kopia deduplicates these backups for long-term vault storage (much smaller) +- **Local**: Database backups (~7 days retention, relatively small) +- **Kopia**: Database backups + library (efficient deduplication) -Example storage calculation (500GB library): -- Local: 7 days × 500GB = ~3.5TB (before compression) -- Kopia (offsite): First backup ~500GB, subsequent backups only store changes (might be <10GB/day after dedup) +**Why library goes directly to Kopia without tar:** -### Compression Formats +Example with 500GB library, adding 10GB photos/month: -Immich backups use `gzip` compression: -- **Database dumps**: `.sql.gz` files (typically 80-90% compression) -- **Library archives**: `.tar.gz` files (photos already compressed, ~40-60% reduction) +**With tar approach:** +- Month 1: Backup 500GB tar +- Month 2: Add 10GB photos → Entire 510GB tar changes → Backup 510GB +- Month 3: Add 10GB photos → Entire 520GB tar changes → Backup 520GB +- **Total storage needed**: 500 + 510 + 520 = 1,530GB + +**Without tar (Kopia direct):** +- Month 1: Backup 500GB +- Month 2: Add 10GB photos → Kopia only backs up the 10GB new files +- Month 3: Add 10GB photos → Kopia only backs up the 10GB new files +- **Total storage needed**: 500 + 10 + 10 = 520GB + +**Savings**: ~66% reduction in storage and backup time! + +This is why we: +- Keep database dumps local (small, fast component restore) +- Let Kopia handle library directly (efficient, incremental, deduplicated) + +### Compression and Deduplication + +**Database backups** use `gzip` compression: +- Typically 80-90% compression ratio for SQL dumps +- Small enough to keep local copies + +**Library backups** use Kopia's built-in compression and deduplication: +- Photos (JPEG/HEIC): Already compressed, Kopia skips re-compression +- Videos: Already compressed, minimal additional compression +- RAW files: Some compression possible +- **Deduplication**: If you upload the same photo twice, Kopia stores it once +- **Block-level dedup**: Even modified photos share unchanged blocks + +This is far more efficient than tar + gzip, which would: +- Compress already-compressed photos (wasted CPU, minimal benefit) +- Store entire archive even if only 1 file changed +- Prevent deduplication across backups ## Additional Resources