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
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