Backing Up Immich


There are a number of posts out there on backing up Immich, but this recent reddit post had me thinking about my backup strategy... and whether it works.

Currently I am limited on storage on my backup NAS, so I use a strategy of keeping daily photo backups, for issues like accidententally deleting, or corrupting something doing work (has never happened thankfully), along with a weekly backup. The weekly is in case some issue goes unnoticed for a few days, and is included in the daily backups.

Here is a backup script that accomplishes this that ChatGPT helped write:

#!/bin/bash
set -euo pipefail

# MODE:
# - "daily"  -> create a daily backup (keep 1)
# - "weekly" -> create a weekly backup (keep 1)
# - "auto"   -> Sunday = weekly, other days = daily
MODE="${1:-auto}"

# Decide mode if auto
if [[ "$MODE" == "auto" ]]; then
  # %u: 1..7 (Mon..Sun)
  if [[ "$(date +%u)" -eq 7 ]]; then
    MODE="weekly"
  else
    MODE="daily"
  fi
fi

if [[ "$MODE" != "daily" && "$MODE" != "weekly" ]]; then
  echo "Invalid mode: $MODE (expected: daily|weekly|auto)"
  exit 1
fi

SOURCE="/home/user"
DEST="/mnt/nfs_backups"

# Retention: keep only 1 for each class
RETENTION_DAILY=1
RETENTION_WEEKLY=1

# Separate lock per class so a daily run can't overlap a weekly one
LOCK="/tmp/backup_home_${MODE}.lock"
HOST="$(hostname -s)"
TS="$(date +"%Y-%m-%d_%H-%M-%S")"

# Name files with class suffix so retention can target them cleanly
OUT_TMP="$DEST/${TS}_${HOST}_home_${MODE}.tar.zst.partial"
OUT="$DEST/${TS}_${HOST}_home_${MODE}.tar.zst"

exec 9>"$LOCK"; flock -n 9 || exit 0
mkdir -p "$DEST"
trap 'rm -f "$OUT_TMP"' EXIT

echo "[$(date '+%F %T')] Starting ${MODE} backup of $SOURCE"
echo "Destination: $OUT_TMP"

# Stream: tar -> zstd -> single file on NFS (no large temp intermediates)
tar -C / -cpf - --numeric-owner home/user \
  | zstd -T0 -19 -q -o "$OUT_TMP"

mv "$OUT_TMP" "$OUT"

SIZE=$(du -h "$OUT" | cut -f1)
echo "[$(date '+%F %T')] Backup complete: $OUT ($SIZE)"

# Retention (separate policies per class)
cd "$DEST"

if [[ "$MODE" == "daily" ]]; then
  ls -1t *_home_daily.tar.zst 2>/dev/null | tail -n +$((RETENTION_DAILY+1)) | xargs -r rm -f
  echo "[$(date '+%F %T')] Applied daily retention (keeping last $RETENTION_DAILY)."
else
  ls -1t *_home_weekly.tar.zst 2>/dev/null | tail -n +$((RETENTION_WEEKLY+1)) | xargs -r rm -f
  echo "[$(date '+%F %T')] Applied weekly retention (keeping last $RETENTION_WEEKLY)."
fi

And you can call this with crontab entries like:

# Daily (Mon-Sat) at 3:00 AM
0 3 * * 1-6 /home/user/run_backups.sh daily >> /home/user/backup.log 2>&1

# Weekly (Sun) at 3:00 AM
0 3 * * 0   /home/user/run_backups.sh weekly >> /home/user/backup.log 2>&1