docs: update immich_backup

This commit is contained in:
Administrator 2026-02-14 03:54:37 +00:00 committed by John Smith
parent 7aa392c5b9
commit 8ffe3252a2

View file

@ -2,7 +2,7 @@
title: Immich Backup and Restore title: Immich Backup and Restore
description: Immich backup with Kopia description: Immich backup with Kopia
published: true published: true
date: 2026-02-14T03:34:42.017Z date: 2026-02-14T03:54:27.661Z
tags: tags:
editor: markdown editor: markdown
dateCreated: 2026-02-14T03:14:32.594Z 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 - Produces compressed `.sql.gz` files
- Database remains available during backup - 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:** **Key Features:**
- No downtime required - No downtime required
- Consistent point-in-time snapshot - Consistent point-in-time snapshot
- Standard PostgreSQL format (portable across systems) - Standard PostgreSQL format (portable across systems)
- Can be restored to any PostgreSQL instance - Efficient incremental backups of photo library
## Setting Up Immich Backups ## Setting Up Immich Backups
@ -150,17 +160,23 @@ docker exec -t immich_postgres pg_dump \
--username=postgres \ --username=postgres \
| gzip > "/opt/immich-backups/dump.sql.gz" | 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 # Backup configuration files
cp docker-compose.yml /opt/immich-backups/ cp docker-compose.yml /opt/immich-backups/
cp .env /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:** **What gets created:**
- Backup directory: `/opt/immich-backups/immich-YYYY-MM-DD-HH-MM-SS/` - Local backup directory: `/opt/immich-backups/immich-YYYY-MM-DD-HH-MM-SS/`
- Contains: `dump.sql.gz` (database), `library.tar.gz` (photos), config files - 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 #### Step 3: Automated Backup Script
@ -213,18 +229,16 @@ fi
echo "[${BACKUP_DATE}] Immich database backup completed successfully" | tee -a "$LOG_FILE" echo "[${BACKUP_DATE}] Immich database backup completed successfully" | tee -a "$LOG_FILE"
# Step 2: Backup upload library (photos/videos) # Step 2: Verify library location exists (Kopia will backup directly, no tar needed)
echo "[${BACKUP_DATE}] Backing up upload library..." | tee -a "$LOG_FILE" echo "[${BACKUP_DATE}] Verifying library location..." | tee -a "$LOG_FILE"
# Get the upload location from docker-compose volumes # Get the upload location from docker-compose volumes
UPLOAD_LOCATION="/srv/immich/library" UPLOAD_LOCATION="/srv/immich/library"
if [ -d "${UPLOAD_LOCATION}" ]; then if [ -d "${UPLOAD_LOCATION}" ]; then
tar -czf "${BACKUP_DIR}/immich-${BACKUP_DATE}/library.tar.gz" \ LIBRARY_SIZE=$(du -sh ${UPLOAD_LOCATION} | cut -f1)
-C "$(dirname ${UPLOAD_LOCATION})" \ echo "[${BACKUP_DATE}] Library location verified: ${UPLOAD_LOCATION} (${LIBRARY_SIZE})" | tee -a "$LOG_FILE"
"$(basename ${UPLOAD_LOCATION})" 2>&1 | tee -a "$LOG_FILE" echo "[${BACKUP_DATE}] Kopia will backup library files directly (no tar, better deduplication)" | tee -a "$LOG_FILE"
echo "[${BACKUP_DATE}] Upload library backup completed" | tee -a "$LOG_FILE"
else else
echo "[${BACKUP_DATE}] WARNING: Upload location not found at ${UPLOAD_LOCATION}" | tee -a "$LOG_FILE" echo "[${BACKUP_DATE}] WARNING: Upload location not found at ${UPLOAD_LOCATION}" | tee -a "$LOG_FILE"
fi fi
@ -262,7 +276,25 @@ fi
echo "[${BACKUP_DATE}] Kopia snapshot completed successfully" | tee -a "$LOG_FILE" 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" echo "[${BACKUP_DATE}] Backing up Immich installation directory..." | tee -a "$LOG_FILE"
kopia snapshot create "${IMMICH_DIR}" \ kopia snapshot create "${IMMICH_DIR}" \
@ -322,15 +354,19 @@ docker compose down
# List available backups # List available backups
ls -lh /opt/immich-backups/ ls -lh /opt/immich-backups/
# Choose a backup # Choose a database backup
BACKUP_PATH="/opt/immich-backups/immich-YYYYMMDD_HHMMSS" BACKUP_PATH="/opt/immich-backups/immich-YYYYMMDD_HHMMSS"
# Restore database # Restore database
gunzip < ${BACKUP_PATH}/dump.sql.gz | \ gunzip < ${BACKUP_PATH}/dump.sql.gz | \
docker compose exec -T database psql --username=postgres --dbname=immich docker compose exec -T database psql --username=postgres --dbname=immich
# Restore library # Restore library from Kopia
tar -xzf ${BACKUP_PATH}/library.tar.gz -C /srv/immich/ kopia snapshot list --tags library
kopia restore <library-snapshot-id> /srv/immich/library
# Fix permissions
chown -R 1000:1000 /srv/immich/library
# Restore configuration (review changes first) # Restore configuration (review changes first)
cp ${BACKUP_PATH}/.env .env.restored cp ${BACKUP_PATH}/.env .env.restored
@ -376,19 +412,17 @@ cd /opt/immich
# Stop Immich # Stop Immich
docker compose down docker compose down
# Restore library from backup # Restore library from Kopia
BACKUP_PATH="/opt/immich-backups/immich-YYYYMMDD_HHMMSS" kopia snapshot list --tags library
tar -xzf ${BACKUP_PATH}/library.tar.gz -C /srv/immich/ kopia restore <library-snapshot-id> /srv/immich/library
# Fix permissions
chown -R 1000:1000 /srv/immich/library
# Start Immich # Start Immich
docker compose up -d 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) ### Method 2: Complete Server Rebuild (Kopia Restore)
Use this when recovering to a completely new server or when local backups are unavailable. 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 | \ gunzip < ${LATEST_BACKUP}/dump.sql.gz | \
docker compose exec -T database psql --username=postgres --dbname=immich docker compose exec -T database psql --username=postgres --dbname=immich
# Restore library # Restore library from Kopia
tar -xzf ${LATEST_BACKUP}/library.tar.gz -C /srv/immich/ kopia snapshot list --tags library
kopia restore <library-snapshot-id> /srv/immich/library
# Fix permissions # Fix permissions
chown -R 1000:1000 /srv/immich/library 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: To restore a single user's library without affecting others:
**Option A: Using Kopia Mount (Recommended)**
```bash ```bash
cd /opt/immich # Mount the Kopia snapshot
kopia snapshot list --tags library
# Stop Immich temporarily mkdir -p /mnt/kopia-library
docker compose down kopia mount <library-snapshot-id> /mnt/kopia-library &
# 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
# Find the user's directory (using user ID from database) # Find the user's directory (using user ID from database)
# User libraries are typically in: library/{user-uuid}/ # User libraries are typically in: library/{user-uuid}/
USER_UUID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" USER_UUID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Copy user's data back # 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}/ /srv/immich/library/${USER_UUID}/
# Fix permissions # Fix permissions
chown -R 1000:1000 /srv/immich/library/${USER_UUID}/ chown -R 1000:1000 /srv/immich/library/${USER_UUID}/
# Cleanup # Unmount
rm -rf /tmp/immich-restore 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 <library-snapshot-id> /srv/immich/library \
--snapshot-path="${USER_UUID}"
# Fix permissions
chown -R 1000:1000 /srv/immich/library/${USER_UUID}/
# Start Immich # Start Immich
docker compose up -d docker compose up -d
# Trigger re-scan for this user via the web UI
``` ```
### Scenario 3: Database Recovery Only ### Scenario 3: Database Recovery Only
@ -726,18 +778,48 @@ When disaster strikes, follow this checklist:
### Storage Efficiency ### Storage Efficiency
Using this two-tier approach: Using this two-tier approach:
- **Local**: Immich creates ~7 days of native backups (may be large, but short retention) - **Local**: Database backups (~7 days retention, relatively small)
- **Offsite**: Kopia deduplicates these backups for long-term vault storage (much smaller) - **Kopia**: Database backups + library (efficient deduplication)
Example storage calculation (500GB library): **Why library goes directly to Kopia without tar:**
- Local: 7 days × 500GB = ~3.5TB (before compression)
- Kopia (offsite): First backup ~500GB, subsequent backups only store changes (might be <10GB/day after dedup)
### Compression Formats Example with 500GB library, adding 10GB photos/month:
Immich backups use `gzip` compression: **With tar approach:**
- **Database dumps**: `.sql.gz` files (typically 80-90% compression) - Month 1: Backup 500GB tar
- **Library archives**: `.tar.gz` files (photos already compressed, ~40-60% reduction) - 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 ## Additional Resources