Immichを別環境に移行するためのバックアップ・復元スクリプト

Googleフォトの代替として注目を集める、オープンソースの写真・動画管理ツールの「Immich」。実際、かなりの完成度で、十分常用出来ます。このImmichを別環境に移行する場合の、バックアップ・復元スクリプトを作成してみました。

スクリプトの内容

まずは最初にこのスクリプトを使った流れや、スクリプトの内容です。

ここで紹介するスクリプトを使った流れ

旧環境(スクリプト1)でバックアップ

chmod +x immich-backup.sh
sudo ./immich-backup.sh

デフォルトでは/opt/lxd-data/immich/backup-YYYYMMDD_HHMMSS/ が作成されます。外付けHDDなどを指定することも可能です。

新環境(スクリプト2)をセットアップ後、復元

chmod +x immich-restore.sh
sudo ./immich-restore.sh /opt/lxd-data/immich/backup-YYYYMMDD_HHMMSS

スクリプトのポイント

項目内容
DBダンプpg_dumpall でロール・スキーマ含む完全ダンプ。Immichが使うvectorスキーマも含まれる
整合性確保ダンプ中は immich_server を一時停止し、書き込み競合を防ぐ
DB復元新環境のDB認証情報(パスワード)はそのまま維持し、旧データのみ上書き
ライブラリ既存ライブラリは削除せず .old.日時 にリネームして退避(安全策)
冪等性復元を何度実行しても壊れないよう DROP DATABASE IF EXISTS で対処

バックアップスクリプト(immich-backup.sh)

実際に、バックアップスクリプトを作成してバックアップを実行します。LXDコンテナで動かしている場合は、LXDコンテナ内に入って作業します。ちなみに、ここではこの方法でインストールしたImmichを、この方法でインストールした環境に復元しています。

mkdir -p /opt/lxd-data/immich
cd /opt/lxd-data/immich
nano immich-backup.sh
chmod +x immich-backup.sh
#!/bin/bash
set -euo pipefail
# =============================================================
#  Immich バックアップスクリプト
#
#  対象環境 (スクリプト1でセットアップした環境):
#   - Immich : https://<hostname>.<tailnet>.ts.net (tailscale serve 443)
#   - データ : /opt/docker/immich/
#
#  使い方:
#   ./immich-backup.sh [バックアップ先ディレクトリ]
#
#  例:
#   ./immich-backup.sh                        # デフォルト: /opt/lxd-data/immich
#   ./immich-backup.sh /mnt/usbssd/immich     # 外付けSSD等を指定
#
#  バックアップ先:
#   <指定ディレクトリ>/
#     backup-<TIMESTAMP>/
#       postgres.dump     ← PostgreSQL全体ダンプ (pg_dumpall)
#       library.tar.gz    ← アップロードファイル一式
#       .env              ← 環境変数(DBパスワード等)
#       BACKUP_INFO       ← バックアップメタ情報
#
#  実行場所: Immichが動作しているLXDコンテナ内
# =============================================================

IMMICH_DIR="/opt/docker/immich"
DEFAULT_BACKUP_BASE="/opt/lxd-data/immich"
BACKUP_BASE="${1:-${DEFAULT_BACKUP_BASE}}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="${BACKUP_BASE}/backup-${TIMESTAMP}"

# ── カラー出力 ─────────────────────────────────────
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

echo ""
echo "════════════════════════════════════════"
echo "  Immich バックアップ"
echo "════════════════════════════════════════"
echo ""

# ── バックアップ先の表示(デフォルトか指定かを明示)
if [ -z "${1:-}" ]; then
    echo -e "  バックアップ先 : ${BACKUP_DIR}"
    echo -e "  ${YELLOW}(デフォルト。変更する場合: $0 /mnt/usbssd/immich)${NC}"
else
    echo -e "  バックアップ先 : ${BACKUP_DIR}"
    echo -e "  ${GREEN}(引数で指定)${NC}"
fi
echo ""

# ── 前提確認 ──────────────────────────────────────
if [ ! -f "${IMMICH_DIR}/.env" ]; then
    echo -e "${RED}ERROR: ${IMMICH_DIR}/.env が見つかりません${NC}"
    exit 1
fi

if ! docker ps --format '{{.Names}}' | grep -q "^immich_postgres$"; then
    echo -e "${RED}ERROR: immich_postgres コンテナが起動していません${NC}"
    echo "  docker compose -f ${IMMICH_DIR}/docker-compose.yml up -d を実行してください"
    exit 1
fi

# .env から DB情報を読み込む
source "${IMMICH_DIR}/.env"

echo -e "  DB名       : ${DB_DATABASE_NAME}"
echo -e "  ライブラリ : ${UPLOAD_LOCATION}"
echo ""

# ── バックアップディレクトリ作成 ──────────────────
echo "==> [1/4] バックアップディレクトリを作成..."
mkdir -p "${BACKUP_DIR}"
echo -e "  ${GREEN}✓ ${BACKUP_DIR}${NC}"

# ── PostgreSQL ダンプ ─────────────────────────────
echo ""
echo "==> [2/4] PostgreSQLをダンプ中..."

# immich_server を一時停止してDBの整合性を確保
echo -e "  ${YELLOW}⚠ immich_server を一時停止します(写真のアップロードが一時的に不可になります)${NC}"
docker stop immich_server immich-machine-learning 2>/dev/null || \
docker stop immich_server 2>/dev/null || true

docker exec immich_postgres pg_dumpall \
    -U "${DB_USERNAME}" \
    > "${BACKUP_DIR}/postgres.dump"

echo -e "  ${GREEN}✓ postgres.dump ($(du -sh "${BACKUP_DIR}/postgres.dump" | cut -f1))${NC}"

# immich_server を再起動
docker start immich_server 2>/dev/null || true
docker start immich-machine-learning 2>/dev/null || true
echo -e "  ${GREEN}✓ immich_server を再起動しました${NC}"

# ── ライブラリをアーカイブ ────────────────────────
echo ""
echo "==> [3/4] ライブラリをアーカイブ中..."
echo -e "  ${YELLOW}(サイズによっては時間がかかります)${NC}"

tar -czf "${BACKUP_DIR}/library.tar.gz" \
    -C "$(dirname "${UPLOAD_LOCATION}")" \
    "$(basename "${UPLOAD_LOCATION}")"

echo -e "  ${GREEN}✓ library.tar.gz ($(du -sh "${BACKUP_DIR}/library.tar.gz" | cut -f1))${NC}"

# ── .env をコピー ─────────────────────────────────
echo ""
echo "==> [4/4] 設定ファイルをコピー..."
cp "${IMMICH_DIR}/.env" "${BACKUP_DIR}/.env"
chmod 600 "${BACKUP_DIR}/.env"
echo -e "  ${GREEN}✓ .env${NC}"

# ── メタ情報を記録 ────────────────────────────────
IMMICH_VERSION_RUNNING=$(docker inspect immich_server \
    --format '{{index .Config.Image}}' 2>/dev/null || echo "unknown")

cat > "${BACKUP_DIR}/BACKUP_INFO" <<EOF
BACKUP_TIMESTAMP=${TIMESTAMP}
BACKUP_DATE=$(date '+%Y-%m-%d %H:%M:%S %Z')
SOURCE_DIR=${IMMICH_DIR}
IMMICH_IMAGE=${IMMICH_VERSION_RUNNING}
DB_USERNAME=${DB_USERNAME}
DB_DATABASE_NAME=${DB_DATABASE_NAME}
UPLOAD_LOCATION=${UPLOAD_LOCATION}
POSTGRES_DUMP_SIZE=$(du -sh "${BACKUP_DIR}/postgres.dump" | cut -f1)
LIBRARY_ARCHIVE_SIZE=$(du -sh "${BACKUP_DIR}/library.tar.gz" | cut -f1)
EOF

echo -e "  ${GREEN}✓ BACKUP_INFO${NC}"

# ── 完了 ──────────────────────────────────────────
echo ""
echo "════════════════════════════════════════"
echo -e "  ${GREEN}✅  バックアップ完了!${NC}"
echo "════════════════════════════════════════"
echo ""
echo "  📁 バックアップ先 : ${BACKUP_DIR}"
echo "  📊 合計サイズ     : $(du -sh "${BACKUP_DIR}" | cut -f1)"
echo ""
echo "  ファイル一覧:"
ls -lh "${BACKUP_DIR}"
echo ""
echo "  次のステップ:"
echo "    復元先の新環境で immich-restore.sh を実行してください"
echo "    バックアップディレクトリ: ${BACKUP_DIR}"
echo ""
echo "════════════════════════════════════════"
echo ""

/opt/lxd-data/immichに保存

デフォルトの場所/opt/lxd-data/immichに保存する場合。

sudo ./immich-backup.sh

外付けHDDなどに保存

外付けHDDなどを指定する場合は、LXDコンテナに追加します。
たとえばホスト接続したデバイスのマウント先が/run/media/user/128だった場合、下記のように指定します(ここではコンテナ内は/opt/ssdにマウントしています。

そして、コンテナに入り、バックアップ先を指定してスクリプトを実行します。

cd /opt/lxd-data/immich
sudo ./immich-backup.sh /opt/ssd

復元スクリプト(immich-restore.sh)

移行先のコンテナでImmichをセットアップします。ここではこの方法でセットアップしています。
セットアップが完了したら、そのコンテナの中で下記を実行してスクリプトを保存します。

mkdir -p /opt/lxd-data/immich
cd /opt/lxd-data/immich
nano immich-restore.sh
chmod +x immich-restore.sh
#!/bin/bash
set -euo pipefail
# =============================================================
#  Immich 復元スクリプト
#
#  復元先環境 (スクリプト2でセットアップした環境):
#   - Immich : https://<hostname>.<tailnet>.ts.net:3307 (tailscale serve 3307)
#   - データ : /opt/docker/immich/
#
#  使い方:
#   ./immich-restore.sh /opt/lxd-data/immich/backup-<TIMESTAMP>
#
#  実行タイミング:
#   スクリプト2でセットアップ完了後(コンテナ起動済みの状態)に実行
#
#  注意:
#   復元先のDBとライブラリは上書きされます
# =============================================================

IMMICH_DIR="/opt/docker/immich"
BACKUP_DIR="${1:-}"

# ── カラー出力 ─────────────────────────────────────
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

echo ""
echo "════════════════════════════════════════"
echo "  Immich 復元"
echo "════════════════════════════════════════"
echo ""

# ── 引数チェック ──────────────────────────────────
if [ -z "${BACKUP_DIR}" ]; then
    echo -e "${RED}ERROR: バックアップディレクトリを引数で指定してください${NC}"
    echo ""
    echo "  使い方:"
    echo "    $0 /opt/lxd-data/immich/backup-<TIMESTAMP>"
    echo ""
    echo "  利用可能なバックアップ:"
    ls -1d /opt/lxd-data/immich/backup-* 2>/dev/null || echo "    (バックアップが見つかりません)"
    echo ""
    exit 1
fi

if [ ! -d "${BACKUP_DIR}" ]; then
    echo -e "${RED}ERROR: バックアップディレクトリが存在しません: ${BACKUP_DIR}${NC}"
    exit 1
fi

# ── バックアップファイルの存在確認 ───────────────
for f in postgres.dump library.tar.gz .env; do
    if [ ! -f "${BACKUP_DIR}/${f}" ]; then
        echo -e "${RED}ERROR: バックアップファイルが見つかりません: ${BACKUP_DIR}/${f}${NC}"
        exit 1
    fi
done

# ── バックアップ情報を表示 ────────────────────────
if [ -f "${BACKUP_DIR}/BACKUP_INFO" ]; then
    echo "  📋 バックアップ情報:"
    while IFS='=' read -r key val; do
        echo "    ${key} = ${val}"
    done < "${BACKUP_DIR}/BACKUP_INFO"
    echo ""
fi

# ── 復元先の確認 ──────────────────────────────────
if [ ! -f "${IMMICH_DIR}/.env" ]; then
    echo -e "${RED}ERROR: ${IMMICH_DIR}/.env が見つかりません${NC}"
    echo "  先にスクリプト2 (immich-setup-port3307.sh) を実行してください"
    exit 1
fi

if ! docker ps --format '{{.Names}}' | grep -q "^immich_postgres$"; then
    echo -e "${RED}ERROR: immich_postgres コンテナが起動していません${NC}"
    echo "  docker compose -f ${IMMICH_DIR}/docker-compose.yml up -d を実行してください"
    exit 1
fi

# 復元先の.envからDB情報を読み込む(新環境のDB認証情報を使う)
source "${IMMICH_DIR}/.env"

echo "  復元元バックアップ : ${BACKUP_DIR}"
echo "  復元先             : ${IMMICH_DIR}"
echo "  復元先DB名         : ${DB_DATABASE_NAME}"
echo "  復元先ライブラリ   : ${UPLOAD_LOCATION}"
echo ""
echo -e "${YELLOW}⚠ 警告: 復元先の既存データはすべて上書きされます${NC}"
echo ""
read -rp "続行しますか? [y/N]: " CONFIRM
if [[ ! "${CONFIRM}" =~ ^[Yy]$ ]]; then
    echo "キャンセルしました"
    exit 0
fi
echo ""

# ── immich_server を停止 ──────────────────────────
echo "==> [1/5] Immichアプリコンテナを停止..."
docker stop immich_server 2>/dev/null || true
docker stop immich-machine-learning 2>/dev/null || true
# machine learningコンテナ名のバリエーションに対応
docker ps --format '{{.Names}}' | grep -E "immich.*(machine|ml)" | xargs -r docker stop || true
echo -e "  ${GREEN}✓ 停止完了${NC}"

# ── PostgreSQLを復元 ─────────────────────────────
echo ""
echo "==> [2/5] PostgreSQLを復元中..."

# 既存DBを削除して再作成(冪等な復元のため)
echo "  既存データベースをリセット中..."
docker exec immich_postgres psql \
    -U "${DB_USERNAME}" \
    -d postgres \
    -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${DB_DATABASE_NAME}' AND pid <> pg_backend_pid();" \
    > /dev/null 2>&1 || true

docker exec immich_postgres psql \
    -U "${DB_USERNAME}" \
    -d postgres \
    -c "DROP DATABASE IF EXISTS ${DB_DATABASE_NAME};" \
    > /dev/null 2>&1

docker exec immich_postgres psql \
    -U "${DB_USERNAME}" \
    -d postgres \
    -c "DROP ROLE IF EXISTS ${DB_USERNAME};" \
    > /dev/null 2>&1 || true

echo "  pg_dumpallデータを復元中(時間がかかる場合があります)..."
docker exec -i immich_postgres psql \
    -U "${DB_USERNAME}" \
    -d postgres \
    < "${BACKUP_DIR}/postgres.dump"

# pg_dumpallでロールごと復元されるため、DBの内部パスワードが旧環境のものに
# 上書きされる。新環境の.envのパスワードに合わせて上書きする。
echo "  DBパスワードを新環境の .env に合わせて更新中..."
docker exec immich_postgres psql \
    -U "${DB_USERNAME}" \
    -d postgres \
    -c "ALTER USER ${DB_USERNAME} WITH PASSWORD '${DB_PASSWORD}';" \
    > /dev/null 2>&1
echo -e "  ${GREEN}✓ PostgreSQL復元完了${NC}"

# ── ライブラリを復元 ─────────────────────────────
echo ""
echo "==> [3/5] ライブラリを復元中..."
echo -e "  ${YELLOW}(サイズによっては時間がかかります)${NC}"

# 既存ライブラリを退避(安全のため削除ではなくリネーム)
UPLOAD_PARENT=$(dirname "${UPLOAD_LOCATION}")
UPLOAD_BASENAME=$(basename "${UPLOAD_LOCATION}")

if [ -d "${UPLOAD_LOCATION}" ] && [ "$(ls -A "${UPLOAD_LOCATION}" 2>/dev/null)" ]; then
    ARCHIVE_NAME="${UPLOAD_LOCATION}.old.$(date +%Y%m%d_%H%M%S)"
    echo -e "  ${YELLOW}既存ライブラリを退避: ${ARCHIVE_NAME}${NC}"
    mv "${UPLOAD_LOCATION}" "${ARCHIVE_NAME}"
fi

mkdir -p "${UPLOAD_LOCATION}"

tar -xzf "${BACKUP_DIR}/library.tar.gz" \
    -C "${UPLOAD_PARENT}"

echo -e "  ${GREEN}✓ ライブラリ復元完了${NC}"

# ── .envのDB認証情報を新環境用に維持 ─────────────
# (新環境の.envは既にスクリプト2で生成済みのため上書き不要)
# ただしIMMICH_VERSIONは復元元に合わせる
echo ""
echo "==> [4/5] 設定を確認..."
BACKUP_IMMICH_VERSION=$(grep "^IMMICH_VERSION=" "${BACKUP_DIR}/.env" | cut -d= -f2 || echo "release")
CURRENT_IMMICH_VERSION=$(grep "^IMMICH_VERSION=" "${IMMICH_DIR}/.env" | cut -d= -f2 || echo "release")

if [ "${BACKUP_IMMICH_VERSION}" != "${CURRENT_IMMICH_VERSION}" ]; then
    echo -e "  ${YELLOW}⚠ IMMICHバージョンが異なります${NC}"
    echo "    バックアップ元: ${BACKUP_IMMICH_VERSION}"
    echo "    復元先        : ${CURRENT_IMMICH_VERSION}"
    echo -e "  ${YELLOW}  新環境のバージョン (${CURRENT_IMMICH_VERSION}) をそのまま使用します${NC}"
fi
echo -e "  ${GREEN}✓ 設定確認完了(新環境の .env を維持)${NC}"

# ── Immichコンテナを再起動 ────────────────────────
echo ""
echo "==> [5/5] Immichを再起動..."
cd "${IMMICH_DIR}"
docker compose up -d

echo "  ⏳ Immich serverの起動を待機中(最大180秒)..."
for i in $(seq 1 36); do
    HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:2283/" 2>/dev/null || echo "000")
    if [ "$HTTP_CODE" != "000" ]; then
        echo -e "\n  ${GREEN}✓ Immich server 起動完了 (HTTP ${HTTP_CODE})${NC}"
        break
    fi
    if [ "$i" -eq 36 ]; then
        echo -e "\n${RED}ERROR: Immich serverがタイムアウトしました${NC}"
        docker logs immich_server --tail=30
        exit 1
    fi
    sleep 5
    echo -n "."
done

# ── 完了 ──────────────────────────────────────────
# 新環境のTailscaleドメインを取得
NEW_DOMAIN=$(tailscale status --json 2>/dev/null | python3 -c "
import json, sys
d = json.load(sys.stdin)
print(d.get('Self', {}).get('DNSName', '').rstrip('.'))
" 2>/dev/null || echo "<新環境のホスト名>")

echo ""
echo "════════════════════════════════════════"
echo -e "  ${GREEN}✅  復元完了!${NC}"
echo "════════════════════════════════════════"
echo ""
echo "  🌐 URL  : https://${NEW_DOMAIN}:3307"
echo ""
echo "  ✅ チェックリスト:"
echo "    [ ] ブラウザで https://${NEW_DOMAIN}:3307 にアクセスできるか"
echo "    [ ] ログインできるか(メールアドレス・パスワードは移行元と同じ)"
echo "    [ ] 写真・アルバムが表示されるか"
echo "    [ ] サムネイルが表示されるか(初回は再生成に時間がかかります)"
echo ""
echo "  ⚠ 移行元の旧ライブラリが退避されている場合:"
echo "    確認後、不要であれば削除してください:"
echo "    ls ${UPLOAD_PARENT}/*.old.* 2>/dev/null"
echo ""
echo "════════════════════════════════════════"
echo ""

復元を実行

バックアップしたファイルを指定して復元します。下記はバックアップファイルを/opt/lxd-data/immichに保存した場合の例。

sudo ./immich-restore.sh backup-YYYYMMDD_HHMMSS

外付けHDDなどから復元

バックアップ時と同様に、ホストのマウントパスをコンテナに追加し、その場所を指定して復元を実行します。

cd /opt/lxd-data/immich
sudo ./immich-restore.sh /opt/ssd/backup-YYYYMMDD_HHMMSS
タイトルとURLをコピーしました