---
title: Immich Backup and Restore
description: Immich backup with Kopia
published: true
date: 2026-02-20T04:11:52.181Z
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 server. Since Immich's data is stored on standard filesystems (not ZFS or BTRFS), snapshots are not available and we rely on Immich's native backup approach combined with Kopia for offsite storage in vaults.

## Quick Reference

### Common Backup Commands

```bash
# Run a manual backup (all components)
/opt/scripts/backup-immich.sh

# Backup just the database
docker exec -t immich_postgres pg_dump --clean --if-exists \
  --dbname=immich --username=postgres | gzip > "/opt/immich-backups/dump.sql.gz"

# List Kopia snapshots
kopia snapshot list --tags immich

# View backup logs
tail -f /var/log/immich-backup.log
```

### Common Restore Commands

```bash
# Restore database from backup
gunzip < /opt/immich-backups/immich-YYYYMMDD_HHMMSS/dump.sql.gz | \
  docker exec -i immich_postgres psql --username=postgres --dbname=immich

# Restore from Kopia to new server
kopia snapshot list --tags tier1-backup
kopia restore <snapshot-id> /opt/immich-backups/

# Check container status after restore
docker compose ps
docker compose logs -f
```

## Critical Components to Backup

### 1. Docker Compose File
- **Location**: `/opt/immich/docker-compose.yml` (or your installation path)
- **Purpose**: Defines all containers, networks, and volumes
- **Importance**: Critical for recreating the exact container configuration

### 2. Configuration Files
- **Primary Config**: `/opt/immich/.env`
- **Purpose**: Database credentials, upload locations, timezone settings
- **Importance**: Required for proper service initialization

### 3. Database
- **PostgreSQL Data**: Contains all metadata, user accounts, albums, sharing settings, face recognition data, timeline information
- **Container**: `immich_postgres`
- **Database Name**: `immich` (default)
- **User**: `postgres` (default)
- **Backup Method**: `pg_dump` (official Immich recommendation)

### 4. Photo/Video Library
- **Upload Storage**: All original photos and videos uploaded by users
- **Location**: `/srv/immich/library` (per your .env UPLOAD_LOCATION)
- **Size**: Typically the largest component
- **Critical**: This is your actual data - photos cannot be recreated

### 5. Additional Important Data
- **Model Cache**: Docker volume `immich_model-cache` (machine learning models, can be re-downloaded)
- **External Paths**: `/export/photos` and `/srv/NextCloud-AIO` (mounted as read-only in your setup)

## Backup Strategy

### Two-Tier Backup Approach

We use a **two-tier approach** combining Immich's native backup method with Kopia for offsite storage:

1. **Tier 1 (Local)**: Immich database dump + library backup creates consistent, component-level backups
2. **Tier 2 (Offsite)**: Kopia snapshots the local backups and syncs to vaults

#### Why This Approach?

- **Best of both worlds**: Native database dump ensures Immich-specific consistency, Kopia provides deduplication and offsite protection
- **Component-level restore**: Can restore individual components (just database, just library, etc.)
- **Disaster recovery**: Full system restore from Kopia backups on new server
- **Efficient storage**: Kopia's deduplication reduces storage needs for offsite copies

#### Backup Frequency
- **Daily**: Immich backup runs at 2 AM
- **Daily**: Kopia snapshot of backups runs at 3 AM
- **Retention (Local)**: 7 days of Immich backups (managed by script)
- **Retention (Kopia/Offsite)**: 30 daily, 12 weekly, 12 monthly

### Immich Native Backup Method

Immich's official backup approach uses `pg_dump` for the database:
- Uses `pg_dump` with `--clean --if-exists` flags for consistent database dumps
- Hot backup without stopping PostgreSQL
- 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)
- Efficient incremental backups of photo library

## Setting Up Immich Backups

### Prereq:
Make sure you are connected to the repository,

```bash 
sudo kopia repository connect server \
  --url=https://192.168.5.10:51516 \
  --override-username=admin \
  --server-cert-fingerprint=696a4999f594b5273a174fd7cab677d8dd1628f9b9d27e557daa87103ee064b2 
```

#### Step 1: Configure Backup Location

Set the backup destination:

```bash
# Create the backup directory
mkdir -p /opt/immich-backups
chown -R root:root /opt/immich-backups
chmod 755 /opt/immich-backups
```

#### Step 2: Manual Backup Commands

```bash
cd /opt/immich

# Backup database using Immich's recommended method
docker exec -t immich_postgres pg_dump \
  --clean \
  --if-exists \
  --dbname=immich \
  --username=postgres \
  | gzip > "/opt/immich-backups/dump.sql.gz"

# 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:**
- 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

Create `/opt/scripts/backup-immich.sh`:

```bash
#!/bin/bash

# Immich Automated Backup Script
# This creates Immich backups, then snapshots them with Kopia for offsite storage

set -e

BACKUP_DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/immich-backup.log"
IMMICH_DIR="/opt/immich"
BACKUP_DIR="/opt/immich-backups"
KEEP_DAYS=7

# Database credentials from .env
DB_USERNAME="postgres"
DB_DATABASE_NAME="immich"
POSTGRES_CONTAINER="immich_postgres"

echo "[${BACKUP_DATE}] ========================================" | tee -a "$LOG_FILE"
echo "[${BACKUP_DATE}] Starting Immich backup process" | tee -a "$LOG_FILE"

# Step 1: Run Immich database backup using official method
echo "[${BACKUP_DATE}] Running Immich database backup..." | tee -a "$LOG_FILE"

cd "$IMMICH_DIR"

# Create backup directory with timestamp
mkdir -p "${BACKUP_DIR}/immich-${BACKUP_DATE}"

# Backup database using Immich's recommended method
docker exec -t ${POSTGRES_CONTAINER} pg_dump \
  --clean \
  --if-exists \
  --dbname=${DB_DATABASE_NAME} \
  --username=${DB_USERNAME} \
  | gzip > "${BACKUP_DIR}/immich-${BACKUP_DATE}/dump.sql.gz"

BACKUP_EXIT=${PIPESTATUS[0]}

if [ $BACKUP_EXIT -ne 0 ]; then
    echo "[${BACKUP_DATE}] ERROR: Immich database backup failed with exit code ${BACKUP_EXIT}" | tee -a "$LOG_FILE"
    exit 1
fi

echo "[${BACKUP_DATE}] Immich database backup completed successfully" | 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
    #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

# Step 3: Backup configuration files
echo "[${BACKUP_DATE}] Backing up configuration files..." | tee -a "$LOG_FILE"

cp "${IMMICH_DIR}/docker-compose.yml" "${BACKUP_DIR}/immich-${BACKUP_DATE}/"
cp "${IMMICH_DIR}/.env" "${BACKUP_DIR}/immich-${BACKUP_DATE}/"

echo "[${BACKUP_DATE}] Configuration backup completed" | tee -a "$LOG_FILE"

# Step 4: Clean up old backups
echo "[${BACKUP_DATE}] Cleaning up backups older than ${KEEP_DAYS} days..." | tee -a "$LOG_FILE"

find "${BACKUP_DIR}" -maxdepth 1 -type d -name "immich-*" -mtime +${KEEP_DAYS} -exec rm -rf {} \; 2>&1 | tee -a "$LOG_FILE"

echo "[${BACKUP_DATE}] Local backup cleanup completed" | tee -a "$LOG_FILE"

# Step 5: Create Kopia snapshot of backup directory
echo "[${BACKUP_DATE}] Creating Kopia snapshot..." | tee -a "$LOG_FILE"

kopia snapshot create "${BACKUP_DIR}" \
  --tags immich:tier1-backup \
  --description "Immich backup ${BACKUP_DATE}" \
  2>&1 | tee -a "$LOG_FILE"

KOPIA_EXIT=${PIPESTATUS[0]}

if [ $KOPIA_EXIT -ne 0 ]; then
    echo "[${BACKUP_DATE}] WARNING: Kopia snapshot failed with exit code ${KOPIA_EXIT}" | tee -a "$LOG_FILE"
    echo "[${BACKUP_DATE}] Local Immich backup exists but offsite copy may be incomplete" | tee -a "$LOG_FILE"
    exit 2
fi

echo "[${BACKUP_DATE}] Kopia snapshot completed successfully" | tee -a "$LOG_FILE"

# 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 \
      --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}" \
#  --tags immich,config,docker-compose \
#  --description "Immich config ${BACKUP_DATE}" \
#  2>&1 | tee -a "$LOG_FILE"

echo "[${BACKUP_DATE}] Backup process completed successfully" | tee -a "$LOG_FILE"
echo "[${BACKUP_DATE}] ========================================" | tee -a "$LOG_FILE"

# Optional: Send notification on completion
# Add your notification method here (email, webhook, etc.)
```

Make it executable:
```bash
chmod +x /opt/scripts/backup-immich.sh
```

Add to crontab (daily at 2 AM):
```bash
# Edit root's crontab
crontab -e

# Add this line:
0 2 * * * /opt/scripts/backup-immich.sh 2>&1 | logger -t immich-backup
```

### Offsite Backup to Vaults

After local Kopia snapshots are created, they sync to your offsite vaults automatically through Kopia's repository configuration.

## Recovery Procedures

### Understanding Two Recovery Methods

We have **two restore methods** depending on the scenario:

1. **Local Restore** (Preferred): For component-level or same-server recovery
2. **Kopia Full Restore**: For complete disaster recovery to a new server

### Method 1: Local Restore (Recommended)

Use this method when:
- Restoring on the same/similar server
- Restoring specific components (just database, just library, etc.)
- Recovering from local Immich backups

#### Full System Restore

```bash
cd /opt/immich

# Stop Immich
docker compose down

# List available backups
ls -lh /opt/immich-backups/

# 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 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
cp ${BACKUP_PATH}/docker-compose.yml docker-compose.yml.restored

# Start Immich
docker compose up -d

# Monitor logs
docker compose logs -f
```

#### Example: Restore Only Database

```bash
cd /opt/immich

# Stop Immich
docker compose down

# Start only database
docker compose up -d database
sleep 10

# Restore database from backup
BACKUP_PATH="/opt/immich-backups/immich-YYYYMMDD_HHMMSS"
gunzip < ${BACKUP_PATH}/dump.sql.gz | \
  docker compose exec -T database psql --username=postgres --dbname=immich

# Start all services
docker compose down
docker compose up -d

# Verify
docker compose logs -f
```

#### Example: Restore Only Library

```bash
cd /opt/immich

# Stop Immich
docker compose down

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

### Method 2: Complete Server Rebuild (Kopia Restore)

Use this when recovering to a completely new server or when local backups are unavailable.

#### Step 1: Prepare New Server

```bash
# Update system
apt update && apt upgrade -y

# Install Docker
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker

# Install Docker Compose
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
apt update
apt install kopia -y

# Create directory structure
mkdir -p /opt/immich
mkdir -p /opt/immich-backups
mkdir -p /srv/immich/library
mkdir -p /srv/immich/postgres
```

#### Step 2: Restore Kopia Repository

```bash
# Connect to your offsite vault
kopia repository connect server \
  --url=https://192.168.5.10:51516 \
  --override-username=admin \
  --server-cert-fingerprint=696a4999f594b5273a174fd7cab677d8dd1628f9b9d27e557daa87103ee064b2

# List available snapshots
kopia snapshot list --tags immich
```

#### Step 3: Restore Configuration

```bash
# Find and restore the config snapshot
kopia snapshot list --tags config

# Restore to the Immich directory
kopia restore <snapshot-id> /opt/immich/

# Verify critical files
ls -la /opt/immich/.env
ls -la /opt/immich/docker-compose.yml
```

#### Step 4: Restore Immich Backups Directory

```bash
# Restore the entire backup directory from Kopia
kopia snapshot list --tags tier1-backup

# Restore the most recent backup
kopia restore <snapshot-id> /opt/immich-backups/

# Verify backups were restored
ls -la /opt/immich-backups/
```

#### Step 5: Restore Database and Library

```bash
cd /opt/immich

# Find the most recent backup
LATEST_BACKUP=$(ls -td /opt/immich-backups/immich-* | head -1)
echo "Restoring from: $LATEST_BACKUP"

# Start database container
docker compose up -d database
sleep 30

# Restore database
gunzip < ${LATEST_BACKUP}/dump.sql.gz | \
  docker compose exec -T database psql --username=postgres --dbname=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
```

#### Step 6: Start and Verify Immich

```bash
cd /opt/immich

# Pull latest images (or use versions from backup if preferred)
docker compose pull

# Start all services
docker compose up -d

# Monitor logs
docker compose logs -f
```

#### Step 7: Post-Restore Verification

```bash
# Check container status
docker compose ps

# Test web interface
curl -I http://localhost:2283

# Verify database
docker compose exec database psql -U postgres -d immich -c "SELECT COUNT(*) FROM users;"

# Check library storage
ls -lah /srv/immich/library/
```

### Scenario 2: Restore Individual User's Photos

To restore a single user's library without affecting others:

**Option A: Using Kopia Mount (Recommended)**

```bash
# 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 /mnt/kopia-library/${USER_UUID}/ \
  /srv/immich/library/${USER_UUID}/

# Fix permissions
chown -R 1000:1000 /srv/immich/library/${USER_UUID}/

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

### Scenario 3: Database Recovery Only

If only the database is corrupted but library data is intact:

```bash
cd /opt/immich

# Stop Immich
docker compose down

# Start only database
docker compose up -d database
sleep 30

# Restore from most recent backup
LATEST_BACKUP=$(ls -td /opt/immich-backups/immich-* | head -1)
gunzip < ${LATEST_BACKUP}/dump.sql.gz | \
  docker compose exec -T database psql --username=postgres --dbname=immich

# Start all services
docker compose down
docker compose up -d

# Verify
docker compose logs -f
```

### Scenario 4: Configuration Recovery Only

If you only need to restore configuration files:

```bash
cd /opt/immich

# Find the most recent backup
LATEST_BACKUP=$(ls -td /opt/immich-backups/immich-* | head -1)

# Stop Immich
docker compose down

# Backup current config (just in case)
cp .env .env.pre-restore
cp docker-compose.yml docker-compose.yml.pre-restore

# Restore config from backup
cp ${LATEST_BACKUP}/.env ./
cp ${LATEST_BACKUP}/docker-compose.yml ./

# Restart
docker compose up -d
```

## Verification and Testing

### Regular Backup Verification

Perform monthly restore tests to ensure backups are valid:

```bash
# Test restore to temporary location
mkdir -p /tmp/backup-test
kopia snapshot list --tags immich
kopia restore <snapshot-id> /tmp/backup-test/

# Verify files exist and are readable
ls -lah /tmp/backup-test/
gunzip < /tmp/backup-test/immich-*/dump.sql.gz | head -100

# Cleanup
rm -rf /tmp/backup-test/
```

### Backup Monitoring Script

Create `/opt/scripts/check-immich-backup.sh`:

```bash
#!/bin/bash

# Check last backup age
LAST_BACKUP=$(ls -td /opt/immich-backups/immich-* 2>/dev/null | head -1)

if [ -z "$LAST_BACKUP" ]; then
    echo "WARNING: No Immich 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)

if [ -z "$BACKUP_EPOCH" ]; then
    echo "WARNING: Cannot parse backup date"
    exit 1
fi

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"
    # Send alert (email, Slack, etc.)
    exit 1
else
    echo "OK: Last backup $AGE_HOURS hours ago"
fi

# Check Kopia snapshots
KOPIA_LAST=$(kopia snapshot list --tags immich --json 2>/dev/null | jq -r '.[0].startTime' 2>/dev/null)

if [ -n "$KOPIA_LAST" ]; then
    echo "Last Kopia snapshot: $KOPIA_LAST"
else
    echo "WARNING: Cannot verify Kopia snapshots"
fi
```

## Disaster Recovery Checklist

When disaster strikes, follow this checklist:

- [ ] Confirm scope of failure (server, storage, specific component)
- [ ] Gather server information (hostname, IP, DNS records)
- [ ] Access offsite backup vault
- [ ] Provision new server (if needed)
- [ ] Install Docker and dependencies
- [ ] Connect to Kopia repository
- [ ] Restore configurations first
- [ ] Restore database
- [ ] Restore library data
- [ ] Start services and verify
- [ ] Test photo viewing and uploads
- [ ] Verify user accounts and albums
- [ ] Update DNS records if needed
- [ ] Document any issues encountered
- [ ] Update recovery procedures based on experience

## Important Notes

1. **External Mounts**: Your setup has `/export/photos` and `/srv/NextCloud-AIO` mounted as external read-only sources. These are not backed up by this script - ensure they have their own backup strategy.

2. **Database Password**: The default database password in your .env is `postgres`. Change this to a secure random password for production use.

3. **Permissions**: Library files should be owned by UID 1000:1000 for Immich to access them properly:
   ```bash
   chown -R 1000:1000 /srv/immich/library
   ```

4. **Testing**: Always test recovery procedures in a lab environment before trusting them in production.

5. **Documentation**: Keep this guide and server details in a separate location (printed copy, password manager, etc.).

6. **Retention Policy**: Review Kopia retention settings periodically to balance storage costs with recovery needs.

## Backup Architecture Notes

### Why Two Backup Layers?

**Immich Native Backups** (Tier 1):
- ✅ Uses official Immich backup method (`pg_dump`)
- ✅ Fast, component-aware backups
- ✅ Selective restore (can restore just database or just library)
- ✅ Standard PostgreSQL format (portable)
- ❌ No deduplication (full copies each time)
- ❌ Limited to local storage initially

**Kopia Snapshots** (Tier 2):
- ✅ Deduplication and compression
- ✅ Efficient offsite replication to vaults
- ✅ Point-in-time recovery across multiple versions
- ✅ Disaster recovery to completely new infrastructure
- ❌ Less component-aware (treats as files)
- ❌ Slower for granular component restore

### Storage Efficiency

Using this two-tier approach:
- **Local**: Database backups (~7 days retention, relatively small)
- **Kopia**: Database backups + library (efficient deduplication)

**Why library goes directly to Kopia without tar:**

Example with 500GB library, adding 10GB photos/month:

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

- [Immich Official Backup Documentation](https://immich.app/docs/administration/backup-and-restore)
- [Kopia Documentation](https://kopia.io/docs/)
- [Docker Volume Backup Best Practices](https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes)
- [PostgreSQL pg_dump Documentation](https://www.postgresql.org/docs/current/app-pgdump.html)

## Revision History

| Date | Version | Changes |
|------|---------|---------|
| 2026-02-13 | 1.0 | Initial documentation - two-tier backup strategy using Immich's native backup method |

---

**Last Updated**: February 13, 2026  
**Maintained By**: System Administrator  
**Review Schedule**: Quarterly
