diff --git a/immich_backup.md b/immich_backup.md new file mode 100644 index 0000000..75793d0 --- /dev/null +++ b/immich_backup.md @@ -0,0 +1,489 @@ +--- +title: Immich Backup and Restore +description: Immich backup with Kopia +published: true +date: 2026-02-14T03:16:12.150Z +tags: +editor: markdown +dateCreated: 2026-02-14T03:14:32.594Z +--- + +# Immich Backup and Recovery Guide + +## Overview + +This document provides comprehensive backup and recovery procedures for Immich photo management server. Following the same proven strategy as our Mailcow backups, we use a **two-tier approach** combining local component-level backups with Kopia for offsite storage in vaults. + +## Quick Reference + +### Common Backup Commands + +```bash +# Run a manual backup (all components) +/opt/scripts/backup-immich.sh + +# List Kopia snapshots +kopia snapshot list --tags immich + +# View backup logs +tail -f /var/log/immich-backup.log +``` + +### Common Restore Commands + +```bash +# Restore from local backup (interactive) +/opt/immich-backups/immich-restore.sh /opt/immich-backups/immich-YYYYMMDD_HHMMSS/ + +# Restore from Kopia to new server +kopia snapshot list --tags tier1-backup +kopia restore /opt/immich-backups/ + +# Check container status after restore +docker compose ps +docker compose logs -f +``` + +## Critical Components to Backup + +### 1. Docker Compose Configuration +- **Location**: `/opt/immich/docker-compose.yml` +- **Purpose**: Defines all containers, networks, and volumes +- **Importance**: Critical for recreating the exact container configuration + +### 2. Environment Configuration +- **Primary Config**: `/opt/immich/.env` +- **Purpose**: Database credentials, upload locations, API keys +- **Importance**: Required for proper service initialization + +### 3. PostgreSQL Database +- **Purpose**: Contains all metadata, user accounts, albums, sharing settings, face recognition data +- **Docker Volume**: `immich_postgres` +- **Backup Method**: `pg_dumpall` (hot backup, no downtime) + +### 4. Photo/Video Library +- **Purpose**: All original photos and videos uploaded by users +- **Docker Volume**: `immich_upload` or `immich_library` +- **Size**: Typically the largest component +- **Critical**: This is your actual data - photos cannot be recreated + +### 5. Additional Important Data +- **Thumbnails**: Can be regenerated but saves processing time +- **Encoded Video**: Transcoded versions, can be regenerated +- **Profile Pictures**: User avatars +- **Machine Learning Models**: Can be re-downloaded + +## Backup Strategy + +### Two-Tier Backup Approach + +We use a **two-tier approach** combining local snapshots with Kopia for offsite storage: + +1. **Tier 1 (Local)**: Component-level backups (database dump + volume archives) +2. **Tier 2 (Offsite)**: Kopia snapshots the local backups and syncs to vaults + +#### Why This Approach? + +- **Best of both worlds**: Local backups ensure quick component-level restore, Kopia provides deduplication and offsite protection +- **Component-level restore**: Can restore individual components (just database, just photos, etc.) +- **Disaster recovery**: Full system restore from Kopia backups on new server +- **Efficient storage**: Kopia's deduplication reduces storage needs for offsite copies +- **Proven strategy**: Same approach used successfully for Mailcow backups + +#### Backup Frequency +- **Daily**: Local Tier 1 backup runs at 2 AM +- **Daily**: Kopia Tier 2 snapshot runs immediately after Tier 1 +- **Retention (Local)**: 7 days of local backups +- **Retention (Kopia/Offsite)**: 30 daily, 12 weekly, 12 monthly + +### What Gets Backed Up + +Each backup creates: +- **database.sql.gz**: Complete PostgreSQL dump +- **immich_*.tar.gz**: Compressed archives of each Docker volume +- **docker-compose.yml**: Container configuration +- **.env**: Environment variables and secrets +- **manifest.txt**: Backup inventory and sizes +- **checksums.sha256**: Integrity verification + +## Setting Up Immich Backups + +### Prerequisites + +Connect to Kopia Repository: + +```bash +sudo kopia repository connect server \ + --url=https://192.168.5.10:51516 \ + --override-username=admin \ + --server-cert-fingerprint=YOUR_FINGERPRINT_HERE +``` + +### Step 1: Configure Backup Location + +```bash +sudo mkdir -p /opt/immich-backups +sudo chown -R root:root /opt/immich-backups +sudo chmod 755 /opt/immich-backups +``` + +### Step 2: Install Backup Scripts + +```bash +sudo mkdir -p /opt/scripts + +sudo cp immich-backup.sh /opt/scripts/backup-immich.sh +sudo cp immich-restore.sh /opt/immich-backups/immich-restore.sh + +sudo chmod +x /opt/scripts/backup-immich.sh +sudo chmod +x /opt/immich-backups/immich-restore.sh +``` + +### Step 3: Configure Backup Script + +Edit `/opt/scripts/backup-immich.sh` and verify: + +```bash +BACKUP_DIR="/opt/immich-backups" +RETENTION_DAYS=7 +IMMICH_DIR="/opt/immich" +POSTGRES_CONTAINER="immich_postgres" +PROJECT_NAME="immich" +ENABLE_KOPIA=true +``` + +Find your PostgreSQL container: +```bash +docker ps | grep postgres +``` + +### Step 4: Test Manual Backup + +```bash +sudo /opt/scripts/backup-immich.sh +``` + +Verify: +```bash +ls -lh /opt/immich-backups/ +kopia snapshot list --tags immich +``` + +### Step 5: Automated Backup with Cron + +```bash +sudo crontab -e +``` + +Add for daily backups at 2 AM: +``` +0 2 * * * /opt/scripts/backup-immich.sh 2>&1 | logger -t immich-backup +``` + +### Step 6: Configure Kopia Retention + +```bash +kopia policy set /opt/immich-backups \ + --keep-latest 30 \ + --keep-daily 30 \ + --keep-weekly 12 \ + --keep-monthly 12 + +kopia policy set /opt/immich \ + --keep-latest 7 \ + --keep-daily 7 +``` + +## Recovery Procedures + +### Method 1: Local Restore (Recommended) + +Full system restore: + +```bash +ls -lh /opt/immich-backups/ + +sudo /opt/immich-backups/immich-restore.sh /opt/immich-backups/immich-YYYYMMDD_HHMMSS/ +``` + +Database-only restore: + +```bash +cd /opt/immich +docker compose down +docker compose up -d postgres +sleep 10 + +BACKUP_PATH="/opt/immich-backups/immich-YYYYMMDD_HHMMSS" +gunzip < "${BACKUP_PATH}/database.sql.gz" | docker exec -i immich_postgres psql -U postgres + +docker compose down +docker compose up -d +``` + +Volume-only restore: + +```bash +cd /opt/immich +docker compose down + +BACKUP_PATH="/opt/immich-backups/immich-YYYYMMDD_HHMMSS" +VOLUME_NAME="immich_upload" + +docker run --rm \ + -v "${VOLUME_NAME}:/data" \ + -v "${BACKUP_PATH}:/backup" \ + alpine sh -c "cd /data && tar -xzf /backup/${VOLUME_NAME}.tar.gz" + +docker compose up -d +``` + +### Method 2: Complete Server Rebuild + +#### Step 1: Prepare New Server + +```bash +# Install Docker +curl -fsSL https://get.docker.com | sh +sudo systemctl enable docker +sudo apt install docker-compose-plugin -y + +# Install Kopia +curl -s https://kopia.io/signing-key | sudo gpg --dearmor -o /usr/share/keyrings/kopia-keyring.gpg +echo "deb [signed-by=/usr/share/keyrings/kopia-keyring.gpg] https://packages.kopia.io/apt/ stable main" | sudo tee /etc/apt/sources.list.d/kopia.list +sudo apt update && sudo apt install kopia -y + +# Create directories +sudo mkdir -p /opt/immich /opt/immich-backups /opt/scripts +``` + +#### Step 2: Connect to Kopia + +```bash +sudo kopia repository connect server \ + --url=https://YOUR_KOPIA_SERVER:51516 \ + --override-username=admin \ + --server-cert-fingerprint=YOUR_FINGERPRINT + +kopia repository status +``` + +#### Step 3: Restore Configuration + +```bash +kopia snapshot list --tags config +kopia restore /opt/immich/ + +ls -la /opt/immich/docker-compose.yml +ls -la /opt/immich/.env +``` + +#### Step 4: Restore Backups + +```bash +kopia snapshot list --tags tier1-backup +kopia restore /opt/immich-backups/ + +ls -la /opt/immich-backups/ +``` + +#### Step 5: Run Local Restore + +```bash +LATEST_BACKUP=$(ls -td /opt/immich-backups/immich-* | head -1) +sudo /opt/immich-backups/immich-restore.sh "$LATEST_BACKUP" +``` + +#### Step 6: Start and Verify + +```bash +cd /opt/immich +docker compose pull +docker compose up -d +docker compose logs -f +``` + +Verify: +- [ ] All containers running: `docker compose ps` +- [ ] Web interface accessible: `curl -I http://localhost:2283` +- [ ] Database accessible: `docker compose exec postgres psql -U postgres -c "\l"` +- [ ] Photos visible: `docker compose exec immich_server ls /usr/src/app/upload/` +- [ ] Login and check albums, timeline, sharing + +## Verification and Testing + +Monthly backup verification: + +```bash +# Verify local backups +ls -lth /opt/immich-backups/ | head -5 + +# Verify checksums +LATEST_BACKUP=$(ls -td /opt/immich-backups/immich-* | head -1) +cd "$LATEST_BACKUP" && sha256sum -c checksums.sha256 + +# Verify Kopia snapshots +kopia snapshot list --tags immich +``` + +### Backup Monitoring Script + +Create `/opt/scripts/check-immich-backup.sh`: + +```bash +#!/bin/bash + +LAST_BACKUP=$(ls -td /opt/immich-backups/immich-* | head -1) + +if [ -z "$LAST_BACKUP" ]; then + echo "ERROR: No backups found!" + exit 1 +fi + +BACKUP_DATE=$(basename "$LAST_BACKUP" | sed 's/immich-//') +BACKUP_EPOCH=$(date -d "${BACKUP_DATE:0:8} ${BACKUP_DATE:9:2}:${BACKUP_DATE:11:2}:${BACKUP_DATE:13:2}" +%s 2>/dev/null) +NOW=$(date +%s) +AGE_HOURS=$(( ($NOW - $BACKUP_EPOCH) / 3600 )) + +if [ $AGE_HOURS -gt 26 ]; then + echo "WARNING: Last Immich backup is $AGE_HOURS hours old" + exit 1 +else + echo "OK: Last backup $AGE_HOURS hours ago" + BACKUP_SIZE=$(du -sh "$LAST_BACKUP" | cut -f1) + echo "Size: $BACKUP_SIZE" +fi +``` + +Add to cron: +```bash +chmod +x /opt/scripts/check-immich-backup.sh +sudo crontab -e +0 8 * * * /opt/scripts/check-immich-backup.sh | logger -t immich-backup-check +``` + +## Disaster Recovery Checklist + +- [ ] Confirm scope of failure +- [ ] Access offsite Kopia repository +- [ ] Provision new server with Docker and Kopia +- [ ] Connect to Kopia repository +- [ ] Restore configuration from Kopia +- [ ] Restore backup directory from Kopia +- [ ] Run local restore script +- [ ] Start Immich containers +- [ ] Verify web interface and photo access +- [ ] Update DNS if needed +- [ ] Document issues and lessons learned + +## Important Notes + +1. **Machine Learning Models**: Can be excluded from backup (re-download automatically) +2. **Photo Deduplication**: Remains intact after restore +3. **Facial Recognition**: Stored in database, restored automatically +4. **Permissions**: Handled automatically by Docker +5. **Upgrades**: Always restore with same or newer Immich version +6. **DNS**: Update if restoring to new server IP + +## Troubleshooting + +### "Database backup failed" +```bash +docker ps | grep postgres +docker compose logs postgres +docker exec immich_postgres pg_dumpall -U postgres | head +``` + +### "Volume not found" +```bash +docker volume ls | grep immich +docker compose up -d --no-start +docker volume ls +``` + +### "Kopia snapshot fails" +```bash +kopia repository status +kopia repository connect server --url=... +kopia snapshot create /opt/immich-backups +``` + +### "Photos missing after restore" +```bash +docker compose exec immich_server ls -lah /usr/src/app/upload/ +docker volume inspect immich_upload +docker compose logs -f immich_server +``` + +## Advanced Topics + +### Exclude Components to Save Space + +Edit backup script to exclude thumbnails/encoded videos: + +```bash +docker volume ls --filter "name=${PROJECT_NAME}" --format "{{.Name}}" | grep -v "thumbs" +``` + +### Incremental Backups + +``` +# Daily: Database + config only +0 2 * * 1-6 /opt/scripts/backup-immich-db-only.sh + +# Weekly: Full backup +0 2 * * 0 /opt/scripts/backup-immich.sh +``` + +### Backup Notifications + +Add to end of backup script: + +```bash +ADMIN_EMAIL="admin@example.com" +echo "Immich backup completed: ${TIER1_SIZE}" | \ + mail -s "✓ Immich Backup Success" "$ADMIN_EMAIL" + +curl -fsS --retry 3 https://hc-ping.com/your-uuid-here +``` + +## Backup Architecture Notes + +### Storage Efficiency Example + +For a 500GB photo library: + +**Without Kopia**: +- 7 days × 500GB = 3.5TB local storage + +**With Two-Tier**: +- Local: 3.5TB (7 days) +- Kopia vault: ~500GB + (30 × 10GB) = ~800GB +- **Savings**: 70-80% in vault storage + +### Component Restore Priority + +| Component | When to Restore | Can Regenerate? | Priority | +|-----------|----------------|-----------------|----------| +| Database | Always | No | Critical | +| Upload/Library | Always | No | Critical | +| Thumbnails | Optional | Yes | Medium | +| Encoded Videos | Optional | Yes | Low | +| Model Cache | Never | Yes | Low | + +## Additional Resources + +- [Immich Official Documentation](https://immich.app/docs) +- [Kopia Documentation](https://kopia.io/docs/) +- [Docker Volume Backup Best Practices](https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes) + +## Revision History + +| Date | Version | Changes | +|------|---------|---------| +| 2026-02-13 | 1.0 | Initial documentation - two-tier backup strategy | + +--- + +**Last Updated**: February 13, 2026 +**Review Schedule**: Quarterly