docs: update immich_backup
This commit is contained in:
parent
7aa392c5b9
commit
8ffe3252a2
1 changed files with 133 additions and 51 deletions
184
immich_backup.md
184
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 <library-snapshot-id> /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 <library-snapshot-id> /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 <library-snapshot-id> /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 <library-snapshot-id> /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 <library-snapshot-id> /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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue