SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-13

آموزش پشتیبان‌گیری و بازیابی اصولی Immich روی VPS

برای پشتیبان‌گیری کامل از Immich، کپی کردن دایرکتوری Postgres کافی نیست. در این راهنما یاد می‌گیرید چگونه با خروجی SQL و فایل‌های docker-compose از خالی شدن تایم‌لاین جلوگیری کنید.

محتویات ضروری برای پشتیبان‌گیری از Immich

پشتیبان‌گیری از Immich شامل سه بخش است که باید دقیقاً در یک لحظه ثبت شوند. فایل‌های اصلی (originals) در مسیر UPLOAD_LOCATION، یک خروجی SQL از دیتابیس Postgres، و فایل‌های .env و docker-compose.yml که ساختار stack را توصیف می‌کنند. بازیابی به معنای تزریق مجدد آن خروجی به یک دیتابیس تازه در زمانی است که سرور Immich متوقف شده باشد، و راه‌اندازی بقیه stack تنها پس از انجام این مرحله صورت می‌گیرد. اگر ترتیب را رعایت نکنید، با یک Immich فعال مواجه می‌شوید که در حالی که دیسک پر است، تایم‌لاین خالی نمایش می‌دهد.

این تفکیک اهمیت دارد زیرا Immich وضعیت خود را در دو مکان ذخیره می‌کند که هیچ‌کدام از دیگری اطلاعی ندارند. Postgres تمام آلبوم‌ها، خوشه‌های چهره، لینک‌های اشتراک‌گذاری، حساب‌های کاربری، کلیدهای API و مسیر ذخیره‌شدهٔ هر asset را نگه می‌دارد. سیستم فایل نیز پیکسل‌ها را در خود جای داده است. اگر فایل‌ها را بدون دیتابیس بازیابی کنید، Immich چیزی به شما نشان نمی‌دهد. اگر دیتابیس را بدون فایل‌ها بازیابی کنید، هر asset به صورت یک تصویر خراب باز می‌شود.

دستورات ارائه‌شده در اینجا برای Immich نسخه 3.1.0 نوشته شده‌اند که نسخه جاری در اوایل آگوست 2026 است. این پروژه با سرعت بالایی توسعه می‌یابد و رویه مستندشده برای پشتیبان‌گیری بیش از یک بار تغییر کرده است؛ بنابراین پیش از کپی کردن هر دستوری، نسخه‌ای که در حال حاضر اجرا می‌کنید را بررسی کنید. اگر stack هنوز راه‌اندازی نشده است، با راهنمای نصب Immich شروع کنید و سپس به اینجا بازگردید.

بدانید مسیرهای شما به کجا اشاره می‌کنند

دو متغیر در .env تعیین‌کنندهٔ همه چیز در این صفحه هستند. UPLOAD_LOCATION دایرکتوری والدینی است که Immich تمام رسانه‌ها را در آن می‌نویسد. DB_DATA_LOCATION دایرکتوری داده‌های Postgres است.

مقدار پیش‌فرض example.env برابر با UPLOAD_LOCATION=./library تنظیم شده است که یک مقدار پیش‌فرض گیج‌کننده است، زیرا Immich سپس پوشه‌ای به نام library درون آن ایجاد می‌کند. فایل‌های اصلی شما در نهایت در ./library/library قرار می‌گیرند. به جای آن یک مسیر مطلق (absolute path) تنظیم کنید تا اسکریپت پشتیبان‌گیری هرگز به دایرکتوری‌ای که از آن اجرا شده است وابسته نباشد.

UPLOAD_LOCATION=/srv/immich/data
DB_DATA_LOCATION=/srv/immich/postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
IMMICH_VERSION=v3.1.0

درون UPLOAD_LOCATION، برنامه Immich چندین پوشه ایجاد می‌کند. سه مورد از آن‌ها داده‌هایی را نگه می‌دارند که هیچ کاری قادر به بازسازی آن‌ها نیست:

  • library: فایل‌های اصلی، که بر اساس الگوی ذخیره‌سازی شما چیدمان شده‌اند
  • upload: فایل‌های اصلی که هنوز به چیدمان الگو منتقل نشده‌اند، به علاوه آپلودهای در حال انجام
  • profile: تصاویر پروفایل کاربران

اگر library را از دست بدهید، عکس برای همیشه پاک شده است. Immich هیچ نسخهٔ دومی از فایل اصلی در هیچ جای دیگری نگه نمی‌دارد.

چرا کپی کردن دایرکتوری داده‌های Postgres پشتیبان محسوب نمی‌شود

DB_DATA_LOCATION هدف ساده‌ای به نظر می‌رسد. این یک دایرکتوری است، rsync آن را کپی می‌کند و کپی بدون خطا به پایان می‌رسد. با این حال، این کار به دو دلیل که می‌توانید شکست آن را مشاهده کنید، پشتیبان نیست.

دلیل اول، پدیده tearing است. Postgres هر تغییر را ابتدا در write-ahead log (WAL) می‌نویسد و سپس در یک checkpoint آن را روی فایل‌های جدول اعمال می‌کند. بنابراین در هر لحظه، فایل‌های روی دیسک در وضعیت میانی هستند و یک کپی متوالی که چهار دقیقه طول می‌کشد، فایل اول را در ساعت 02:00 و فایل آخر را در 02:04 می‌خواند. این دو فایل متعلق به یک تراکنش واحد نیستند. وقتی Postgres را روی نتیجه اجرا می‌کنید، یا در زمان شروع با خطای PANIC: could not locate a valid checkpoint record مواجه می‌شوید، یا اجرا می‌شود و در اولین خواندن یک صفحه آسیب‌دیده با خطای invalid page in block 1234 of relation base/16384/... از کار می‌افتد. هیچ‌کدام از این دو حالت با آن کپی قابل بازیابی نیستند.

دلیل دوم حتی اگر ابتدا همه چیز را متوقف کنید نیز پابرجا می‌ماند. دایرکتوری داده‌های Postgres به باینری‌های دقیقی که آن را نوشته‌اند وابسته است. Immich ایمیج دیتابیس خود را با digest مشخص می‌کند که در حال حاضر ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 است. این نسخه Postgres 14 با دو افزونه جستجوی برداری (vector-search) کامپایل‌شده است. دایرکتوری داده‌ای که توسط آن build نوشته شده، با نسخه اصلی (major version) متفاوت Postgres باز نمی‌شود و با buildای که نسخه‌های افزونه متفاوتی دارد نیز باز نخواهد شد. میزبان بازیابی شما باید دقیقاً همان ایمیج را بازتولید کند. یک SQL dump چنین محدودیتی ندارد: آن یک متن است و هر سرور سازگاری می‌تواند آن را اجرا کند.

pg_dump مشکل tearing را به‌طور کامل دور می‌زند. این ابزار کل دیتابیس را در یک snapshot واحد MVCC (کنترل همروندی چندنسخه‌ای) می‌خواند، بنابراین دیتابیس را دقیقاً همان‌طور که در یک لحظه خاص بوده می‌بیند، در حالی که سایر عملیات نوشتن در اطراف آن ادامه دارند. به همین دلیل است که برای dump گرفتن از Postgres نیازی به متوقف کردن آن ندارید.

مواردی که می‌توانید از نسخه پشتیبان حذف کنید

این موارد بازتولید می‌شوند، بنابراین می‌توانید از آن‌ها صرف‌نظر کنید:

  • thumbs: تصاویر پیش‌نمایش و بندانگشتی (thumbnail)
  • encoded-video: ویدیوهای تبدیل‌شده (transcoded)
  • DB_DATA_LOCATION: قابل بازسازی از روی فایل dump
  • حجم (volume) داکر model-cache: مدل‌های یادگیری ماشین که در صورت نیاز دوباره دانلود می‌شوند

حذف این موارد یک معامله است، نه یک مزیت رایگان. بازسازی تصاویر بندانگشتی و ویدیوهای تبدیل‌شده برای یک کتابخانه بزرگ، ساعت‌ها درگیری CPU روی یک VPS کوچک ایجاد می‌کند و در تمام این مدت، تایم‌لاین فقط جای‌خالی‌های خاکستری را نشان می‌دهد. شما می‌توانید آن‌ها را از مسیر Administration > Jobs و با تنظیم گزینه‌های "Generate Thumbnails" و "Transcode Videos" برای اجرا روی دارایی‌های مفقود، دوباره اجرا کنید. اگر فضای ذخیره‌سازی مقصد پشتیبان شما کافی است، آن‌ها را شامل کنید تا منتظر نمانید. اگر به محدودیت فضای ذخیره‌سازی نزدیک هستید، آن‌ها را حذف کنید و برای بازسازی‌شان برنامه‌ریزی کنید. تخمین اندازه کتابخانه Immich توضیح می‌دهد که این پوشه‌ها نسبت به فایل‌های اصلی چقدر بزرگ می‌شوند.

یک پوشه دیگر نیز ارزش شناختن دارد. UPLOAD_LOCATION/backups حاوی فایل‌های dump خودکار پایگاه‌داده Immich است که روزانه در ساعت 02:00 نوشته می‌شوند و 14 مورد آخر نگهداری می‌شوند؛ این تنظیمات در بخش Administration > Settings > Backup قابل پیکربندی هستند. این فایل‌ها هزینه‌ای برای شما ندارند و واقعاً مفید هستند. آن‌ها همچنین روی همان دیسکی قرار دارند که از آن محافظت می‌کنند، بنابراین در صورت مهاجرت ناموفق کمک‌کننده هستند، اما در صورت خرابی کامل سرور کارایی ندارند. در هر صورت، خودتان یک فایل dump تهیه کنید، زیرا dumpای که شخصاً اجرا می‌کنید، دقیقاً در همان لحظه‌ای ثبت می‌شود که snapshot فایل‌های مربوط به آن گرفته شده است.

تهیه نسخه پشتیبان از پایگاه داده

docker exec -t immich_postgres pg_dump --clean --if-exists \
  --dbname=immich --username=postgres \
  | gzip > /srv/immich/backup/immich.sql.gz

اگر immich و postgres را تغییر داده‌اید، آن‌ها را با DB_DATABASE_NAME و DB_USERNAME خود جایگزین کنید. --clean --if-exists باعث می‌شود پیش از هر CREATE یک DROP ... IF EXISTS قرار گیرد؛ بدین ترتیب، در صورت وجود اشیاء در پایگاه داده مقصد، عملیات بازیابی به جای توقف در اولین خطا، ادامه می‌یابد.

اکنون به نکته‌ای می‌پردازیم که بی‌سروصدا اسکریپت‌های پشتیبان‌گیری را خراب می‌کند. دستور فوق یک pipeline است و shell وضعیت خروج آخرین دستور در pipeline را گزارش می‌دهد. اگر pg_dump به دلیل رمز عبور اشتباه یا اجرا نشدن container با خطا مواجه شود، gzip یک جریان خالی دریافت می‌کند، یک فایل gzip کاملاً معتبر می‌سازد و با کد خروج 0 پایان می‌یابد. اسکریپت شما موفقیت را ثبت می‌کند و شما صاحب یک نسخه پشتیبان 20 بایتی می‌شوید. دستور pipefail را در ابتدای هر اسکریپت پشتیبان‌گیری قرار دهید:

#!/usr/bin/env bash
set -euo pipefail

سپس به جای اعتماد به کد خروج، نتیجه را بررسی کنید:

ls -lh /srv/immich/backup/immich.sql.gz
gunzip -c /srv/immich/backup/immich.sql.gz | head -n 3

خط اول یک dump سالم باید شامل -- PostgreSQL database dump باشد. فایلی با حجم چند صد بایت، صرف‌نظر از آنچه اسکریپت گزارش داده، یک dump ناموفق است.

در کنار فایل dump، ثبت کنید که کدام build آن را ایجاد کرده است:

docker inspect --format '{{.Config.Image}}' immich_server > /srv/immich/backup/immich-version.txt

برای این کار به .env تکیه نکنید. فایل پیش‌فرض، IMMICH_VERSION=v3 را تنظیم می‌کند که یک تگ شناور است و با هر نسخه 3.x تغییر می‌کند؛ بنابراین، این تگ هیچ اطلاعاتی درباره اینکه کدام build واقعاً dump را نوشته است به شما نمی‌دهد. تگ دقیق را در .env نیز ثابت (pin) کنید.

توقف سرور و سپس تهیه snapshot با restic

فایل‌های موجود در UPLOAD_LOCATION تا زمانی که Immich در حال اجراست، تغییرناپذیر نیستند. سرور آپلودهای جدید را می‌نویسد و job مربوط به قالب‌بندی ذخیره‌سازی، فایل‌ها را بین دایرکتوری‌ها جابه‌جا می‌کند. اگر ابزار پشتیبان‌گیری فایلی را در حین نوشتن بخواند، همان بایت‌های ناقص را به عنوان کل فایل ذخیره می‌کند و هیچ خطایی نیز گزارش نمی‌شود. کانتینر سرور را برای مدت زمان اجرای عملیات متوقف کنید:

docker stop immich_server

immich_postgres را در حال اجرا باقی بگذارید، زیرا dump به آن نیاز دارد. رابط وب و اپلیکیشن موبایل تا زمانی که سرور را دوباره راه‌اندازی نکنید آفلاین خواهند بود، که این وضعیت در ساعت 03:00 برای یک نمونه خانگی معمولاً مشکلی ایجاد نمی‌کند.

restic در اینجا مناسب است زیرا پیش از خروج هر داده‌ای از دستگاه، آن را deduplicate و رمزنگاری می‌کند. آن را به مخزنی اشاره دهید که روی این سرور قرار ندارد:

export RESTIC_REPOSITORY=sftp:backup@backup.example.com:/srv/restic/immich
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init

ذخیره‌سازی شیء (Object storage) نیز به همین شکل عمل می‌کند و اگر می‌خواهید نسخه پشتیبان کاملاً خارج از سخت‌افزار شما باشد، گزینه بهتری است:

export RESTIC_REPOSITORY=s3:https://s3.example.com/immich-backup
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
restic init

آن endpoint می‌تواند یک bucket MinIO که خودتان اجرا می‌کنید روی دستگاه دوم باشد، یا هر ارائه‌دهنده سازگار با S3. مخزنی که روی همان دیسک کتابخانه قرار دارد، تنها شما را در برابر حذف اشتباهی محافظت می‌کند و نه چیز دیگر.

سپس snapshot را بگیرید و دقیقاً آنچه اهمیت دارد را لیست کنید:

restic backup \
  /srv/immich/backup/immich.sql.gz \
  /srv/immich/backup/immich-version.txt \
  /srv/immich/data/library \
  /srv/immich/data/upload \
  /srv/immich/data/profile \
  /srv/immich/.env \
  /srv/immich/docker-compose.yml
docker start immich_server

restic در هر اجرا کل درخت فایل‌ها را می‌خواند اما فقط بلوک‌هایی را آپلود می‌کند که قبلاً ندیده است؛ بنابراین اولین snapshot کل کتابخانه شما را منتقل می‌کند و هر snapshot پس از آن، فقط عکس‌های جدید همان روز را جابه‌جا خواهد کرد.

نگهداری و کلیدهایی که باید در جای دیگری باشند

restic forget --prune --keep-daily 7 --keep-weekly 5 --keep-monthly 12

forget اسنپ‌شات‌ها را از ایندکس حذف می‌کند. --prune نیمه دیگر این فرآیند است که داده‌هایی را که آن اسنپ‌شات‌ها آخرین مرجع‌شان بوده‌اند، پاک می‌کند. اگر forget را بدون --prune اجرا کنید، هزینه فضای ذخیره‌سازی شما هرگز کاهش نخواهد یافت.

بررسی ساختار (Structure check) کم‌هزینه است، بنابراین آن را به‌صورت هفتگی اجرا کنید:

restic check

این دستور بررسی می‌کند که متادیتای مخزن (repository) سازگار باشد. این دستور داده‌های شما را نمی‌خواند. ماهی یک‌بار، یک نمونه را دوباره بخوانید و آن را با هش‌های ثبت‌شده‌اش مطابقت دهید:

restic check --read-data-subset=5%

این تنها بررسی‌ای است که خرابی‌های خاموش (silent corruption) در backend ذخیره‌سازی را شناسایی می‌کند، زیرا بلوک‌های واقعی را دانلود کرده و checksum آن‌ها را دوباره محاسبه می‌کند. یک --read-data کامل روی یک آرشیو عکس به معنای دانلود کل مخزن است که در سرویس‌های ذخیره‌سازی ابری با هزینه حجمی، هزینه واقعی در پی دارد؛ بنابراین، بررسی یک زیرمجموعه چرخشی (rolling subset) همان چیزی است که کاربران در عمل اجرا می‌کنند.

و حالا بخشی که افراد از آن صرف‌نظر می‌کنند: رمز عبور مخزن restic قابل بازیابی نیست. هیچ راهی برای بازنشانی (reset) یا ثبت تیکت پشتیبانی وجود ندارد. اگر تنها نسخه رمز عبور در /root/.restic-password روی همان سروری باشد که قصد بازیابی آن را دارید، نسخه‌های پشتیبان شما صرفاً داده‌های رمزنگاری‌شده غیرقابل استفاده هستند. همین موضوع در مورد کلید دسترسی به فضای ذخیره‌سازی ابری و DB_PASSWORD از .env نیز صدق می‌کند. همه آن‌ها را در جایی نگه دارید که به زنده بودن این ماشین وابسته نباشد: به‌صورت چاپ‌شده در یک کشو، یا در یک مدیریت‌کننده رمز عبور (password manager) که روی سخت‌افزار دیگری اجرا می‌شود. اگر آن مدیریت‌کننده نیز به‌صورت self-hosted است، به همان مراقبت نیاز دارد و پشتیبان‌گیری از Vaultwarden وظیفه جداگانه‌ای محسوب می‌شود.

بازیابی Immich با رعایت ترتیب صحیح

ترتیب بازیابی همان جایی است که بک‌آپ‌های خوب به تایم‌لاین‌های خالی تبدیل می‌شوند. این توالی را در میزبان جدید دنبال کنید.

ابتدا پیکربندی را بازگردانید. این فایل به شما می‌گوید کدام نسخه را اجرا کنید و مسیرها به کجا اشاره دارند.

restic restore latest --target /restore \
  --include /srv/immich/.env \
  --include /srv/immich/docker-compose.yml \
  --include /srv/immich/backup

پیش از شروع هر کاری، نسخه را ثابت (Pin) کنید. فایل immich-version.txt را بخوانید، مقدار IMMICH_VERSION را در .env روی همان تگ دقیق تنظیم کنید و فعلاً با جدیدترین نسخه کاری نداشته باشید. Immich از دانگرید پشتیبانی نمی‌کند، حتی بین نسخه‌های پچ؛ بنابراین اگر سرور جدیدتر با یک دامپ قدیمی‌تر بالا بیاید و migrationها را اجرا کند، راه بازگشتی وجود ندارد.

رسانه‌ها را بازیابی کنید.

restic restore latest --target /restore --include /srv/immich/data

سپس library، upload و profile را منتقل کنید تا دقیقاً در مسیری قرار بگیرند که UPLOAD_LOCATION در این میزبان به آن اشاره می‌کند. مسیر خودِ میزبان می‌تواند تغییر کند، زیرا فایل compose آن دایرکتوری را به یک مسیر ثابت در داخل کانتینر متصل (bind) می‌کند. چیدمان داخل آن نباید تغییر کند.

دیتابیس را به‌تنهایی اجرا کنید. مقدار DB_DATA_LOCATION را خالی بگذارید تا Postgres یک کلاستر جدید ایجاد کند.

cd /srv/immich
docker compose pull
docker compose create
docker start immich_postgres
docker exec immich_postgres pg_isready --username=postgres

دستور pg_isready پس از اتمام راه‌اندازی اولیه که چند ثانیه طول می‌کشد، accepting connections را چاپ می‌کند. docker compose create تمام کانتینرها را بدون اجرا کردن می‌سازد و این دقیقاً هدف این مرحله است: سرور Immich نباید هنوز اجرا شود. سروری که با یک دیتابیس خالی بالا می‌آید، migrationهای خود را اعمال می‌کند، یک اسکیما جدید می‌سازد و از شما می‌خواهد یک حساب کاربری مدیر جدید ایجاد کنید؛ در این صورت شما در حال ریختن دامپ روی یک اپلیکیشن در حال اجرا خواهید بود.

دامپ را بازگردانی (Replay) کنید.

gunzip --stdout /restore/srv/immich/backup/immich.sql.gz \
  | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
  | docker exec -i immich_postgres psql --dbname=immich --username=postgres \
      --single-transaction --set ON_ERROR_STOP=on

دو بخش از این دستور کار اصلی را انجام می‌دهند. sed به این دلیل وجود دارد که pg_dump به عنوان یک اقدام ایمنی، یک search_path خالی در خروجی خود می‌نویسد تا نام‌های فاقد صلاحیت در دامپ به یک اسکیما غیرمنتظره ارجاع داده نشوند. انواع داده‌های جستجوی برداری (vector-search) در Immich در public قرار دارند، بنابراین با یک مسیر جستجوی خالی، بازیابی به اولین ستونی که با نوع vector تعریف شده می‌رسد و psql با خطای ERROR: type "vector" does not exist متوقف می‌شود. بازگرداندن public به مسیر، این مشکل را حل می‌کند.

--single-transaction --set ON_ERROR_STOP=on کل عملیات بازیابی را در یک تراکنش واحد قرار می‌دهد که در صورت بروز اولین خطا، متوقف می‌شود. شما یا یک دیتابیس کامل خواهید داشت یا یک دیتابیس دست‌نخورده. بدون این گزینه، شکست در میانه راه باعث می‌شود دیتابیسی داشته باشید که بالا می‌آید و لاگین شما را می‌پذیرد، اما تعداد نامشخصی از آلبوم‌ها در آن گم شده است که هفته‌ها بعد متوجه آن خواهید شد.

حالا همه چیز را اجرا کنید.

docker compose up -d
docker compose ps
docker logs -f immich_server

منتظر خط راه‌اندازی مانند Immich Server is listening on بمانید، سپس پورت 2283 را باز کنید و با اعتبارنامه‌های قدیمی خود وارد شوید، زیرا حساب‌های کاربری همراه با دامپ بازگشته‌اند. اگر صفحه لاگین به جای آن پیشنهاد ایجاد اولین حساب مدیر را می‌دهد، دیتابیس بازیابی نشده است. متوقف شوید و خروجی psql را دوباره بخوانید.

یک هشدار در مورد دستورالعمل‌های رسمی بازیابی که با docker compose down -v شروع می‌شوند: دستور -v، ولوم‌های نام‌گذاری‌شده (named volumes) را حذف می‌کند. در فایل compose پیش‌فرض، UPLOAD_LOCATION و DB_DATA_LOCATION از نوع bind mount هستند، بنابراین از این دستور جان سالم به در می‌برند. اگر هر یک از آن‌ها را به ولوم نام‌گذاری‌شده تغییر داده‌اید، آن دستور عکس‌های شما را پاک می‌کند. پیش از تایپ کردن، فایل compose خود را بخوانید.

چرا پس از بازیابی، تایم‌لاین خالی است

تایم‌لاین بر اساس ردیف‌های پایگاه داده ترسیم می‌شود. Immich هرگز در زمان بوت، upload/ را برای کشف مجدد عکس‌ها اسکن نمی‌کند، زیرا فایلی که ردیفی برای آن وجود ندارد، مالک، تاریخ و آلبوم ندارد. بنابراین، رایج‌ترین اشتباه در بازیابی این است که فایل‌ها بازگردانده شده‌اند اما پایگاه داده وجود ندارد. Immich شروع به کار می‌کند، یک شمای خالی می‌سازد و یک نمونه (instance) فعال اما خالی به شما تحویل می‌دهد، در حالی که دیسک پر از عکس‌های شماست. چیزی از دست نرفته است، اما چیزی هم قابل مشاهده نیست. راه‌حل این است که dump را در حالی که سرور متوقف است، دقیقاً مطابق دستورالعمل بالا دوباره اعمال کنید.

نسخه دوم مشکل، بی‌سروصداتر است. پایگاه داده بازیابی می‌شود، تایم‌لاین با ورودی‌ها پر می‌شود، اما باز کردن هر دارایی (asset) با خطا مواجه می‌شود. این یعنی ردیف‌ها به فایل‌هایی اشاره می‌کنند که کانتینر قادر به دیدن آن‌ها نیست؛ معمولاً به این دلیل که library، upload و profile پس از یک restic restore --target /restore که کسی آن‌ها را در جای درست قرار نداده، یک سطح عمیق‌تر قرار گرفته‌اند. به‌جای حدس زدن، از داخل کانتینر بررسی کنید:

docker exec immich_server ls /data

فایل compose پیش‌فرض، UPLOAD_LOCATION را در /data مانت می‌کند، بنابراین لیست کردن محتوا باید library، upload و profile را نشان دهد. اگر یک دایرکتوری خالی یا یک پوشه srv پراکنده را نشان می‌دهد، bind mount شما به سطح اشتباهی اشاره دارد و ردیف‌های پایگاه داده مشکلی ندارند.

تطابق نسخه بین پشتیبان و بازیابی

Immich به‌طور مکرر نسخه جدید منتشر می‌کند و طرحواره (schema) پایگاه داده نیز همراه با آن تغییر می‌کند؛ بنابراین، یک فایل dump شامل طرحواره‌ای است که سرورِ مبدأ در زمان ایجاد آن داشته است.

بازیابی یک dump قدیمی روی سروری با نسخه جدیدتر معمولاً بدون مشکل انجام می‌شود، زیرا سرور در زمان شروع، migrationهای معوقه را اعمال کرده و طرحواره را به‌روزرسانی می‌کند. این مسیر در طول توالی انتشار نسخه‌ها تست می‌شود. پریدن از چند نسخه اصلی (major version) در یک مرحله، جایی است که مشکل ایجاد می‌شود؛ پروژه تغییرات ناسازگار (breaking changes) را به نسخه‌های اصلی محدود کرده و آن‌ها را در changelog خود مستند می‌کند.

بازیابی یک dump جدیدتر روی سروری با نسخه قدیمی‌تر به‌هیچ‌وجه کار نمی‌کند. فایل dump حاوی جداول و ستون‌هایی است که کد قدیمی آن‌ها را نمی‌شناسد و Immich اعلام کرده است که دانگرید (downgrade) حتی بین نسخه‌های اصلاحی (patch releases) نیز پشتیبانی نمی‌شود. هیچ دستور rollback برای این کار وجود ندارد.

بنابراین، بازیابی ایمن، روندی خسته‌کننده اما مطمئن دارد. دقیقاً همان نسخه‌ای را اجرا کنید که dump را ایجاد کرده است، آن را بازیابی کنید، وارد سیستم شوید، کامل بودن تایم‌لاین را تأیید کنید و تنها پس از آن اقدام به ارتقا نمایید. ارتقا را یک نسخه در هر مرحله انجام دهید، مقدار IMMICH_VERSION را تغییر داده و پس از هر ارتقا، docker compose pull && docker compose up -d را اجرا کنید. نگهداری پشتیبان‌های یک هفته اخیر نیز در اینجا کمک می‌کند: اگر مشخص شد که جدیدترین پشتیبان در حین یک ارتقای ناموفق گرفته شده است، پشتیبان روز گذشته همچنان در مخزن موجود است.

تأیید ماهانه نسخه پشتیبان

نسخه پشتیبانی که هرگز بازیابی نشده است، صرفاً یک حدس و گمان است. ماهی یک‌بار، آن را در یک نمونه موقت بازیابی کنید و یک عکس را مشاهده کنید. این تمرین حدود 20 دقیقه زمان می‌برد و تنها عاملی است که مطالب این صفحه را به یک برنامه بازیابی واقعی تبدیل می‌کند.

restic snapshots
restic stats latest

snapshots باید اجرای دیشب را فهرست کند. stats latest باید اندازه‌ای نزدیک به کتابخانه شما گزارش دهد، نه چند مگابایت.

در یک دایرکتوری موقت، ترجیحاً روی یک میزبان یدکی، بازیابی کنید:

restic restore latest --target /tmp/immich-drill

فایل‌های docker-compose.yml و .env را از مجموعه بازیابی‌شده کپی کنید و سپس سه مورد را در کپی تغییر دهید. مسیر UPLOAD_LOCATION و DB_DATA_LOCATION را به دایرکتوری‌هایی در زیرمجموعه /tmp/immich-drill تغییر دهید. پورت وب را روی جای دیگری منتشر کنید، مثلاً 12283:2283 به‌جای 2283:2283. خطوط container_name: را حذف کنید، زیرا فایل compose پیش‌فرض نام‌هایی مانند immich_server را به‌صورت hard-code دارد؛ بنابراین اگر استک دوم روی همان میزبان ایجاد شود، با اولی تداخل پیدا می‌کند و Docker از ایجاد آن خودداری می‌کند.

توالی بازیابی را از بالا اجرا کنید: فقط دیتابیس، سپس replay کردن dump و در نهایت docker compose up -d. اکنون چهار بررسی زیر را انجام دهید که صحت عملکرد را اثبات می‌کنند:

  1. با رمز عبوری که پیش از تمرین استفاده می‌کردید وارد شوید. کارکردن حساب‌ها به این معنی است که dump به‌درستی بازیابی شده است.
  2. تایم‌لاین را باز کنید و به قدیمی‌ترین ماه بروید. وجود دارایی‌ها در کل بازه زمانی به این معنی است که تمام ردیف‌ها بازگشته‌اند، نه فقط موارد اخیر.
  3. یک عکس را در اندازه کامل باز کنید و نسخه اصلی آن را دانلود کنید.
  4. آن را با همان فایل در کتابخانه اصلی خود با استفاده از sha256sum مقایسه کنید. تطابق هش‌ها به این معنی است که بایت‌ها از رفت و برگشت از طریق restic جان سالم به در برده‌اند.

سپس با استفاده از docker compose down -v در دایرکتوری تمرین، محیط را پاک‌سازی کنید و /tmp/immich-drill را حذف نمایید. تاریخ را جایی یادداشت کنید که آن را ببینید، زیرا ارزش این کار کاملاً به تکرار آن در ماه آینده بستگی دارد. اگر هنوز در حال تصمیم‌گیری برای انتخاب سرور عکس هستید، مقایسه PhotoPrism و Immich دقیقاً به تفاوت این دو در همین زمینه می‌پردازد.

FAQ

آیا برای پشتیبان‌گیری از Immich باید آن را متوقف کنم؟

سرویس immich_server را متوقف کنید و اجازه دهید immich_postgres به کار خود ادامه دهد. نیازی به توقف پایگاه داده نیست، زیرا pg_dump داده‌ها را در یک snapshot از نوع MVCC می‌خواند و فارغ از عملیات نوشتن همزمان، یک وضعیت منسجم و واحد را مشاهده می‌کند. دلیل اصلی توقف سرویس، فایل‌ها هستند: سرور آپلودهای جدید را می‌نویسد و job مربوط به storage template فایل‌ها را بین دایرکتوری‌ها جابه‌جا می‌کند؛ بنابراین ابزار پشتیبان‌گیری ممکن است فایلی را در حین نوشتن بخواند و یک نسخه ناقص و خراب ذخیره کند. اجرای docker stop immich_server پیش از snapshot و docker start immich_server پس از آن، این تداخل (race condition) را از بین می‌برد.

آیا می‌توانم به‌جای اجرای pg_dump، پوشه داده‌های Postgres را کپی کنم؟

خیر. کپی کردن دایرکتوری داده در حالی که سرویس فعال است، باعث می‌شود فایل‌های مختلف در زمان‌های متفاوتی خوانده شوند؛ در نتیجه خروجی یک وضعیت منسجم نخواهد بود و Postgres هنگام راه‌اندازی با خطای PANIC: could not locate a valid checkpoint record مواجه می‌شود یا بعداً به دلیل خرابی صفحات داده (damaged page) شکست می‌خورد. حتی کپی‌برداری در حالت توقف کامل سرویس نیز به نسخه دقیق پایگاه داده وابسته است: Immich از ایمیج Postgres 14 با نسخه‌های خاصی از افزونه vector-search استفاده می‌کند و آن دایرکتوری در هیچ نسخه دیگری باز نخواهد شد. اما یک SQL dump متنی ساده است و در هر سرور سازگاری قابل اجراست.

چرا پس از بازیابی (restore)، تایم‌لاین Immich من خالی است؟

زیرا تایم‌لاین بر اساس ردیف‌های پایگاه داده ساخته می‌شود و شما فایل‌ها را بدون پایگاه داده بازیابی کرده‌اید. Immich هرگز دایرکتوری upload/ را برای کشف مجدد عکس‌ها اسکن نمی‌کند، بنابراین فایل‌هایی که ردیف متناظر در پایگاه داده ندارند، دیده نمی‌شوند. خود عکس‌ها دست‌نخورده باقی مانده‌اند. سرور را متوقف کنید، dump را در یک Postgres تازه مقداردهی‌شده بازیابی کنید و سپس stack را استارت بزنید. اگر برعکس، تایم‌لاین پر است اما هیچ عکسی باز نمی‌شود، مشکل برعکس است: مسیرهای library، upload و profile مستقیماً داخل دایرکتوری متصل‌شده (bind) به کانتینر نیستند. این موضوع را با docker exec immich_server ls /data بررسی کنید.

کدام پوشه‌های Immich را می‌توانم در پشتیبان‌گیری نادیده بگیرم؟

پوشه‌های thumbs و encoded-video از روی فایل‌های اصلی بازسازی می‌شوند و DB_DATA_LOCATION نیز از روی dump دوباره ساخته می‌شود، بنابراین هیچ‌کدام نیازی به قرارگیری در مجموعه پشتیبان ندارند. نادیده گرفتن آن‌ها باعث می‌شود پس از بازیابی زمان بیشتری صرف کنید، زیرا بازسازی پیش‌نمایش‌ها و فایل‌های تبدیل‌شده (transcode) برای یک کتابخانه بزرگ، ساعت‌ها درگیری CPU ایجاد می‌کند که باید از طریق مسیر Administration > Jobs برای دارایی‌های گمشده اجرا شود. آنچه هرگز نباید نادیده بگیرید library، upload و profile است، زیرا تنها نسخه موجود از هر فایل اصلی در این مسیرها قرار دارد.

آیا می‌توانم dump مربوط به Immich را در نسخه جدیدتر بازیابی کنم؟

معمولاً بله، زیرا سرور در هنگام شروع، migrationهای معلق را اعمال کرده و schema را به‌روزرسانی می‌کند. حالت معکوس شکست می‌خورد: Immich از دانگرید (downgrade) پشتیبانی نمی‌کند، حتی بین نسخه‌های patch؛ بنابراین dump گرفته‌شده از یک نسخه جدیدتر را نمی‌توان در سرور قدیمی‌تر بارگذاری کرد. بازیابی را با IMMICH_VERSION که روی همان نسخه‌ای که dump را تولید کرده قفل شده است انجام دهید، از کامل بودن تایم‌لاین مطمئن شوید و سپس ارتقا دهید. نسخه Immich را در کنار هر dump با docker inspect --format '{{.Config.Image}}' immich_server یادداشت کنید، زیرا تگ پیش‌فرض IMMICH_VERSION=v3 یک تگ شناور است و اطلاعاتی درباره نسخه دقیق به شما نمی‌دهد.