You are currently viewing Immich Backup Automation Script | Full Automatic Backup

Immich Backup Automation Script | Full Automatic Backup

Immich Backup Automation Script, the full Immich backup with all data including photos and videos. Although Immich offers integrated backup functions, these only back up the database – not your valuable photos and videos! For a truly complete backup, you need both.

ComponentContainsBuilt-in Backup?
PostgreSQL DatabaseMetadata, albums, face recognition, tags✅ Yes (daily dumps)
Media (Photos/Videos)Your original files❌ No

The golden rule: Back up the database first, then the media. This avoids “dead links” during restoration – photos that exist in the database but are physically missing. The time gap between both backups should be minimal.

Our script does exactly that: First the database, then the media – automatically, reliably, and space-efficiently.

Video: Immich Backup Automation Script | Full Automatic Backup

Language: 🇩🇪|🇬🇧
☝️ Use YouTube subtitles for all languages.

Installing rsync on Linux

# Install rsync (Ubuntu/Debian)
sudo apt update
sudo apt install rsync -y

# Verify installation
rsync --version

Script Description

The script is located in your docker-compose.yml directory (e.g., /mnt/d/Visual Edit/immich/backup_immich.sh). The main configuration areas:

Configuration (adjust to your needs!)

# ---------- CONFIGURATION ----------
SOURCE="/mnt/d/Visual Edit/immich/medien/"
DEST_BASE="/mnt/d/backups/immich"
LOG="$DEST_BASE/backup.log"

# Retention period (in days)
KEEP_DAILY=7      # Keep daily backups for 7 days
KEEP_WEEKLY=30    # Keep weekly backups for 30 days (approx. 4 weeks)
KEEP_MONTHLY=365  # Keep monthly backups for 365 days (1 year)

# PostgreSQL Container
DB_CONTAINER="immich_postgres"
DB_USER="postgres"

Intelligent Backup Types

The script automatically distinguishes:

TypeWhen?Example Name
dailyEvery day except Sunday/first of monthdaily-2026-03-10
weeklyEvery Sundayweekly-2026-03-15
monthlyEvery 1st of the monthmonthly-2026-04-01

The Complete Backup Script

IMPORTANT !

Note that rsync does not work with the number of backups. To avoid having to configure two values, the setting is interpreted as “older than xx days.” Files older than this threshold are deleted, depending on the backup schedule (daily, weekly, monthly).

# Aufbewahrungsdauer (in Tagen)
KEEP_DAILY=7 # Keep daily backups that are less than 7 days old
KEEP_WEEKLY=30 # Keep weekly backups that are less than 30 days old
KEEP_MONTHLY=365 # Keep monthly backups that are less than 365 days old

#!/bin/bash

# =====================================================
# IMMICH POWER-BACKUP mit rsync (Hardlink-Modus)
# =====================================================
# Autor      | KSC, Michael Kissner
# Hiomepage  | www.klissner.uk
# Version    | 2.0 
#
# 🇬🇧 Link for Support / donation for the channel:
# 🇩🇪 Link zur Unterstützung / Spende für denn Kanal: 
# PayPal: https://www.paypal.com/donate?hosted_button#MEAN_id=G8CZWPDCM3SNW
# Bank, Bitcoin und Lightning: https://www.ksc-llp.uk/donateyoutube
# =====================================================

#!/bin/bash

# =====================================================
# IMMICH POWER-BACKUP mit rsync (Hardlink-Modus) - KORRIGIERT
# =====================================================

# ---------- KONFIGURATION ----------
SOURCE="/mnt/d/Visual Edit/immich/medien/"
DEST_BASE="/mnt/d/backups/immich/"
LOG="$DEST_BASE/backup.log"

# Aufbewahrungsdauer (in Tagen)
KEEP_DAILY=3       # Behalte tägliche Backups, die jünger als 7 Tage sind
KEEP_WEEKLY=14     # Behalte wöchentliche Backups, die jünger als 30 Tage sind  
KEEP_MONTHLY=60    # Behalte monatliche Backups, die jünger als 365 Tage sind

# PostgreSQL Container Name
DB_CONTAINER="immich_postgres"
DB_USER="postgres"
DB_NAME="immich"

# ---------- DATUM ERMITTELN ----------
DATE=$(date +%Y-%m-%d)
YEAR=$(date +%Y)
MONTH=$(date +%m)
DAY=$(date +%d)
DOW=$(date +%u)   # 1=Montag, 7=Sonntag

# ---------- BACKUP-TYP BESTIMMEN ----------
# Monatlich? (Am ersten Tag des Monats)
if [ "$DAY" == "01" ]; then
    BACKUP_TYPE="monthly"
    BACKUP_NAME="monthly-$YEAR-$MONTH"
# Wöchentlich? (Sonntag = 7)
elif [ "$DOW" == "7" ]; then
    BACKUP_TYPE="weekly"
    BACKUP_NAME="weekly-$YEAR-W$((($(date +%V))))"  # Kalenderwoche
# Täglich (alle anderen Tage)
else
    BACKUP_TYPE="daily"
    BACKUP_NAME="daily-$DATE"
fi

# ---------- PFADE FESTLEGEN ----------
DEST="$DEST_BASE/$BACKUP_NAME"
LATEST_LINK="$DEST_BASE/latest"
DB_BACKUP_DIR="$DEST/database"
DB_BACKUP_FILE="$DB_BACKUP_DIR/immich-db-$DATE.sql"

# ---------- LOGGING-FUNKTION ----------
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG"
}

# ========== HAUPTPROGRAMM ==========
# Log-Verzeichnis erstellen
mkdir -p "$DEST_BASE"

log "========================================="
log "BACKUP GESTARTET - Typ: $BACKUP_TYPE"
log "========================================="

# Prüfungen
if [ ! -d "$SOURCE" ]; then
    log "FEHLER: Quelle $SOURCE existiert nicht!"
    log "Aktuelles Verzeichnis: $(pwd)"
    exit 1
fi

# Prüfe ob Docker-Container läuft
if ! docker ps | grep -q "$DB_CONTAINER"; then
    log "WARNUNG: PostgreSQL Container $DB_CONTAINER läuft nicht - Datenbank-Backup wird übersprungen"
    DB_AVAILABLE=false
else
    DB_AVAILABLE=true
fi

# Zielordner erstellen
mkdir -p "$DEST"
mkdir -p "$DB_BACKUP_DIR"
log "Zielordner: $DEST"

# ========== DATENBANK-BACKUP ==========
if [ "$DB_AVAILABLE" = true ]; then
    log "Starte Datenbank-Backup..."
    
    # pg_dumpall erstellt ein komplettes Backup aller Datenbanken und Rollen
    docker exec "$DB_CONTAINER" pg_dumpall -U "$DB_USER" > "$DB_BACKUP_FILE" 2>> "$LOG"
    
    if [ $? -eq 0 ]; then
        # Komprimieren um Platz zu sparen
        gzip -f "$DB_BACKUP_FILE"
        log "✅ Datenbank-Backup erfolgreich: $DB_BACKUP_FILE.gz"
        
        # Größe anzeigen
        DB_SIZE=$(du -h "$DB_BACKUP_FILE.gz" | cut -f1)
        log "   Größe: $DB_SIZE"
    else
        log "❌ FEHLER: Datenbank-Backup fehlgeschlagen!"
    fi
else
    log "⚠️ Datenbank-Backup übersprungen (Container nicht verfügbar)"
fi

# ========== FOTO-BACKUP MIT HARDLINKS ==========
log "Starte rsync für Fotos (Hardlink-Modus)..."

# WICHTIG: Quellpfad mit Slash am Ende für korrekte Inhaltskopie
SOURCE_WITH_SLASH="${SOURCE%/}/"

if [ -d "$LATEST_LINK" ] && [ -d "$LATEST_LINK/medien" ]; then
    # Hardlinks zum letzten Backup verwenden
    rsync -a --delete \
          --link-dest="$LATEST_LINK/medien" \
          "$SOURCE_WITH_SLASH" \
          "$DEST/medien/" >> "$LOG" 2>&1
else
    # Erstes Backup
    rsync -a "$SOURCE_WITH_SLASH" "$DEST/medien/" >> "$LOG" 2>&1
fi

if [ $? -eq 0 ]; then
    log "✅ rsync erfolgreich abgeschlossen"
    
    # Latest-Link aktualisieren
    rm -f "$LATEST_LINK"
    ln -s "$DEST" "$LATEST_LINK"
    log "✅ Latest-Link aktualisiert"
    
    # Größe des Foto-Backups ermitteln
    if [ -d "$DEST/medien" ]; then
        PHOTO_SIZE=$(du -sh "$DEST/medien" | cut -f1)
        log "   Größe Fotos: $PHOTO_SIZE"
    fi
else
    log "❌ FEHLER: rsync fehlgeschlagen!"
    exit 1
fi

# ========== AUFRÄUMEN ALTER BACKUPS ==========
log "Starte Aufräumen alter Backups..."

# Sicherstellen, dass wir im richtigen Verzeichnis sind
if ! cd "$DEST_BASE"; then
    log "FEHLER: Kann nicht nach $DEST_BASE wechseln!"
    exit 1
fi

# Aufräumen basierend auf Backup-Typ und Alter - KORRIGIERT
# Lösche tägliche Backups, die älter als KEEP_DAILY sind
find . -maxdepth 1 -type d -name "daily-*" -mtime +$KEEP_DAILY 2>/dev/null | while read dir; do
    log "Lösche altes tägliches Backup: $dir"
    rm -rf "$dir"
done

# Lösche wöchentliche Backups, die älter als KEEP_WEEKLY sind
find . -maxdepth 1 -type d -name "weekly-*" -mtime +$KEEP_WEEKLY 2>/dev/null | while read dir; do
    log "Lösche altes wöchentliches Backup: $dir"
    rm -rf "$dir"
done

# Lösche monatliche Backups, die älter als KEEP_MONTHLY sind
find . -maxdepth 1 -type d -name "monthly-*" -mtime +$KEEP_MONTHLY 2>/dev/null | while read dir; do
    log "Lösche altes monatliches Backup: $dir"
    rm -rf "$dir"
done

# ========== ZUSAMMENFASSUNG ==========
log "========================================="
log "BACKUP ABGESCHLOSSEN"
log "Typ: $BACKUP_TYPE"
log "Ziel: $DEST"
log "  - Datenbank: $([ "$DB_AVAILABLE" = true ] && echo "✅" || echo "⚠️ übersprungen")"
log "  - Fotos: ✅ (Hardlinks aktiv)"
log "Aufbewahrung:"
log "  - Täglich: $KEEP_DAILY Tage"
log "  - Wöchentlich: $KEEP_WEEKLY Tage"
log "  - Monatlich: $KEEP_MONTHLY Tage"
log "========================================="

Hinweis: Vergesse nicht die Datei backup_immich.sh als “ausführbar” zu berechtigen:

chmod +x backup_immich.sh

Otherwise, the script cannot be started!

Crontab Setup (Linux/WSL)

Important: Windows Task Scheduler doesn’t work reliably here because WSL instances have issues in background mode. The solution is Cron within your WSL environment:

# Open crontab
crontab -e

# Add the following line (Wed+Sun at 11:00 PM):
0 23 * * 0,3 /mnt/d/Visual\ Edit/immich/backup_immich.sh >> /home/mike/cron.log 2>&1

# Save and exit

Crontab Explanation:

PositionValueMeaning
Minute0At the full hour
Hour23At 11:00 PM
Day*Every day
Month*Every month
Weekday0,3Sunday (0) and Wednesday (3)

Testing Cron:

# Check if Cron is running
sudo service cron status

# Display crontab
crontab -l

# Check logs
cat /home/mike/cron.log

Summary

Your backup system now:

  • Complete (database + photos)
  • Automatic (Wed+Sun at 11:00 PM)
  • Space-efficient (hardlinks)
  • Versioned (daily/weekly/monthly)
  • Restorable (every snapshot)

Enjoy the peace of mind – your photos are now truly protected!


Donate Bild

Support / Donation Link for the Channel
If my posts have been helpful or supported you in any way, I’d truly appreciate your support 🙏

PayPal Link
Bank transfer, Bitcoin and Lightning


#Immich #PhotoManagement #Photos #PhotoAlbum  #Docker #Linux #WSL

Leave a Reply