SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor

پشتیبان‌گیری و بازیابی Immich روی VPS

برای backup واقعی Immich باید فایل‌های UPLOAD_LOCATION، dump پایگاه‌داده و فایل‌های .env و docker-compose.yml را هم‌زمان ذخیره کنید؛ بازیابی نادرست timeline را خالی می‌کند.

یک نسخهٔ پشتیبان Immich باید شامل چه چیزهایی باشد

نسخهٔ پشتیبان Immich شامل 3 بخش است که باید در یک زمان ثبت شوند: فایل‌های اصلی در UPLOAD_LOCATION، یک dump از پایگاه‌دادهٔ Postgres، و .env و docker-compose.yml که stack را توصیف می‌کنند. بازیابی یعنی این dump را در یک پایگاه‌دادهٔ تازه، زمانی که سرور Immich متوقف است، وارد کنید و فقط پس از آن باقی stack را راه‌اندازی کنید. اگر ترتیب را اشتباه انجام دهید، Immich فعال خواهد بود، اما timeline آن خالی است و دیسک همچنان پر است.

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

دستورهای این صفحه بر اساس Immich v3.1.0 نوشته شده‌اند؛ این نسخه در اوایل August 2026 نسخهٔ جاری بود. در 2026-10-21، نسخهٔ جاری v3.3.4 است که در 2026-10-20 منتشر شده و فایل compose، image پایگاه‌داده و مراحل مستند backup و restore آن با موارد استفاده‌شده در این صفحه مطابقت دارد. این پروژه با سرعت زیادی release می‌دهد و رویهٔ مستند backup بیش از یک بار تغییر کرده است؛ بنابراین پیش از copy کردن هر چیزی، نسخه‌ای را که واقعاً اجرا می‌کنید بررسی کنید. اگر 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 هیچ نسخهٔ دومی از فایل اصلی در هیچ جای دیگری نگه نمی‌دارد.

چرا کپی‌کردن data directory مربوط به Postgres نسخهٔ پشتیبان نیست

DB_DATA_LOCATION در نگاه اول هدف ساده‌ای به نظر می‌رسد. این یک directory است، rsync آن را کپی می‌کند و عملیات کپی نیز بدون خطا تمام می‌شود. اگر هنگام اجرای Postgres آن را کپی کنید، کپی ایجادشده نسخهٔ پشتیبان نیست. اگر Postgres را متوقف کنید و سپس کپی بگیرید، کپی ایجادشده نسخهٔ پشتیبان محسوب می‌شود، اما شکننده است. این دو دلیل را در ادامه می‌بینید و می‌توانید شکست هر دو روش را مشاهده کنید.

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

دلیل دوم به کپی‌ای مربوط است که پس از توقف کامل سرویس می‌گیرید. PostgreSQL کپی کامل یک data directory خاموش‌شده را به‌عنوان نسخهٔ پشتیبان معتبر مستند کرده است، اما این directory فقط با binaryهایی باز می‌شود که از storage format یکسانی استفاده کنند. Immich image پایگاه‌دادهٔ خود را با digest مشخص می‌کند؛ در حال حاضر ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0. این image شامل Postgres 14 است و extensionهای جست‌وجوی برداری VectorChord، pgvector و pgvecto.rs را در خود دارد. یک minor release جدیدتر از Postgres 14 می‌تواند directory را بدون تغییر باز کند، اما یک major version متفاوت نمی‌تواند. extensionهایی که جدول‌های شما به آن‌ها وابسته‌اند نیز باید وجود داشته باشند و versionهای سازگار داشته باشند. میزبان restore باید یک image سازگار اجرا کند. SQL dump چنین محدودیتی ندارد: متن است و هر server سازگار می‌تواند آن را دوباره اجرا کند.

pg_dump مشکل کپی ناقص و ناهماهنگ فایل‌ها را مستقیماً برطرف می‌کند. این ابزار کل پایگاه‌داده را داخل یک snapshot واحد از MVCC (multi-version concurrency control) می‌خواند. بنابراین پایگاه‌داده را دقیقاً در وضعیتی می‌بیند که در یک لحظه داشته است، درحالی‌که writeهای دیگر هم‌زمان ادامه دارند. به همین دلیل برای گرفتن dump لازم نیست Postgres را متوقف کنید.

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

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

  • thumbs: تصاویر پیش‌نمایش و thumbnail
  • encoded-video: ویدئوی transcode‌شده
  • DB_DATA_LOCATION: مواردی که از dump بازسازی می‌شوند
  • volume داکر model-cache: مدل‌های machine learning که در صورت نیاز دوباره دانلود می‌شوند

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

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

دریافت dump پایگاه داده

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 قرار می‌دهد؛ بنابراین dump در پایگاه داده‌ای بازپخش می‌شود که از قبل object دارد و هنگام برخورد با اولین object متوقف نمی‌شود.

اکنون به جزئیاتی می‌رسیم که بی‌سروصدا اسکریپت‌های backup را خراب می‌کند. آن command یک pipeline است و shell وضعیت خروجی آخرین command در pipeline را گزارش می‌کند. اگر pg_dump، به‌دلیل password نادرست یا اجرا نشدن container، fail شود، gzip یک stream خالی دریافت می‌کند، یک فایل gzip کاملاً معتبر می‌نویسد و با کد 0 خارج می‌شود. اسکریپت شما موفقیت را ثبت می‌کند و یک backup 20-byte در اختیار دارید. pipefail را در ابتدای هر اسکریپت backup قرار دهید:

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

سپس نتیجه را بررسی کنید و فقط به exit code اعتماد نکنید:

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

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

releaseای را که dump را ایجاد کرده است، در کنار dump ثبت کنید. برای این کار از server در حال اجرا سؤال کنید:

curl -fsS http://localhost:2283/api/server/version > /srv/immich/backup/immich-version.txt

این endpoint به login نیاز ندارد و release در حال اجرا را به‌صورت شماره‌های major، minor و patch برمی‌گرداند؛ بنابراین 3، 1 و 0 به معنای v3.1.0 هستند. برای این کار به .env یا docker inspect --format '{{.Config.Image}}' immich_server اتکا نکنید؛ docker inspect --format '{{.Config.Image}}' immich_server فقط image referenceای را که پیکربندی کرده‌اید بازتاب می‌دهد. فایل stock، IMMICH_VERSION=v3 را تنظیم می‌کند؛ این یک tag شناور است که هر release از سری 3.x را دنبال می‌کند. بنابراین رکوردی که به :v3 ختم شود، درباره buildای که واقعاً dump را ایجاد کرده است چیزی به شما نمی‌گوید. tag دقیق را در .env نیز ثابت کنید.

توقف سرور و سپس تهیه 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 به ترتیبی که درست کار می‌کند

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

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

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

پیش از شروع هر چیزی، version را ثابت کنید. immich-version.txt را بخوانید، مقدار IMMICH_VERSION را در .env دقیقاً روی همان release تنظیم کنید (v3.1.0 برای major 3، minor 1 و patch 0) و فعلاً جدیدترین release را کنار بگذارید. Immich از downgrade پشتیبانی نمی‌کند، حتی بین patch releaseها. بنابراین اگر server جدیدتر با یک dump قدیمی شروع شود و migrationهای خود را اجرا کند، راه بازگشتی وجود ندارد.

media را restore کنید.

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

سپس library، upload و profile را طوری منتقل کنید که مستقیماً داخل مسیری قرار بگیرند که UPLOAD_LOCATION در این host به آن اشاره می‌کند. خود مسیر host می‌تواند تغییر کند، چون فایل compose آن directory را به یک مسیر ثابت داخل container bind می‌کند. ساختار داخلی آن نباید تغییر کند.

database را به‌تنهایی شروع کنید. DB_DATA_LOCATION را خالی بگذارید تا Postgres یک cluster جدید را initialize کند.

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

pg_isready پس از پایان setup اولیه، accepting connections را چاپ می‌کند؛ این کار چند ثانیه زمان می‌برد. docker compose create همه containerها را بدون start کردن آن‌ها build می‌کند و هدف این مرحله نیز همین است: Immich server هنوز نباید اجرا شود. Serverی که با database خالی شروع شود، migrationهای خود را اعمال می‌کند، schema جدید می‌سازد و از شما می‌خواهد یک admin account جدید ایجاد کنید. در این حالت، dump را زیر یک application در حال اجرا restore می‌کنید.

dump را 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 خالی را در خروجی خود می‌نویسد تا نام‌های بدون qualification در dump به schema پیش‌بینی‌نشده‌ای resolve نشوند. typeهای جست‌وجوی برداری Immich در public قرار دارند. بنابراین با search path خالی، restore به نخستین column که با type برداری تعریف شده است می‌رسد و psql با ERROR: type "vector" does not exist متوقف می‌شود. قرار دادن دوباره public در path این مشکل را برطرف می‌کند.

--single-transaction --set ON_ERROR_STOP=on کل restore را در یک transaction اجرا می‌کند که با نخستین خطا abort می‌شود. در نتیجه یا یک database کامل دارید یا database بدون تغییر باقی می‌ماند. بدون آن، خطایی که در میانه کار رخ دهد databaseای ایجاد می‌کند که start می‌شود، login شما را می‌پذیرد و تعداد نامعلومی از albumها را ندارد؛ مشکلی که ممکن است چند هفته بعد متوجه آن شوید.

اکنون همه‌چیز را start کنید.

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

منتظر یک خط startup مانند Immich Server is listening on بمانید، سپس port 2283 را باز کنید و با credentialهای قبلی خود login کنید، چون user accountها همراه dump بازگردانده شده‌اند. اگر صفحه login به‌جای آن پیشنهاد ایجاد نخستین admin account را نمایش داد، database restore نشده است. کار را متوقف کنید و خروجی psql را دوباره بخوانید.

درباره دستورهای رسمی restore از طریق command line که با docker compose down -v آغاز می‌شوند، یک هشدار مهم وجود دارد. -v volumeهای نام‌گذاری‌شده را حذف می‌کند. در compose file پیش‌فرض، UPLOAD_LOCATION و DB_DATA_LOCATION bind mount هستند و بنابراین از این دستور جان سالم به‌در می‌برند. اگر هرکدام را به named volume تغییر داده باشید، این دستور photoهای شما را حذف می‌کند. پیش از وارد کردن دستور، compose file خود را بخوانید.

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

تایم‌لاین بر اساس ردیف‌های پایگاه داده ترسیم می‌شود. 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 داده‌ها را درون یک MVCC snapshot می‌خواند و صرف‌نظر از این‌که چه فرایند دیگری در حال نوشتن باشد، یک لحظهٔ سازگار واحد را می‌بیند. دلیل توقف فایل‌ها هستند: سرور uploadهای جدید را می‌نویسد و job مربوط به storage template فایل‌ها را بین directoryها جابه‌جا می‌کند. بنابراین ابزار backup ممکن است فایلی را در میانهٔ write بخواند و بدون هیچ خطایی یک کپی ناقص ذخیره کند. اجرای docker stop immich_server پیش از snapshot و docker start immich_server پس از آن، این race را برطرف می‌کند.

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

تا زمانی که Postgres در حال اجراست، خیر. کپی تدریجی یک data directory فعال، فایل‌های مختلف را در لحظه‌های متفاوت می‌خواند؛ بنابراین نتیجه یک state سازگار واحد نیست و Postgres هنگام startup آن را با PANIC: could not locate a valid checkpoint record رد می‌کند یا بعداً روی یک page آسیب‌دیده fail می‌شود. کپی کاملی که هنگام توقف Postgres گرفته شود، backup معتبری است؛ اما فقط با همان major version از Postgres و extensionهای سازگار باز می‌شود. Immich از یک image مربوط به Postgres 14 استفاده می‌کند که VectorChord، pgvector و pgvecto.rs را در خود دارد و این directory با major version دیگری باز نمی‌شود. SQL dump متن ساده است و در هر server سازگار replay می‌شود؛ بنابراین همچنان همین کپی است که باید نگه دارید.

چرا timeline مربوط به Immich پس از restore خالی است؟

زیرا timeline از rowهای پایگاه داده ساخته می‌شود و شما فایل‌ها را بدون پایگاه داده restore کرده‌اید. Immich هرگز upload/ را scan نمی‌کند تا عکس‌ها را دوباره کشف کند؛ بنابراین فایل‌هایی که row ندارند، قابل مشاهده نیستند. خود عکس‌ها تغییری نکرده‌اند. سرور را متوقف کنید، dump را در یک Postgres که به‌تازگی initialise شده است replay کنید و سپس stack را start کنید. اگر برعکس، timeline کامل است اما بازکردن همهٔ عکس‌ها fail می‌شود، مشکل معکوس است: library، upload و profile مستقیماً داخل directoryای نیستند که به container bind شده است. آن را با docker exec immich_server ls /data بررسی کنید.

کدام پوشه‌های Immich را می‌توانم از backup حذف کنم؟

thumbs و encoded-video از originalها دوباره تولید می‌شوند و DB_DATA_LOCATION از dump بازسازی می‌شود؛ بنابراین هیچ‌کدام لازم نیست در backup set باشند. حذف آن‌ها زمان را پس از restore مصرف می‌کند، نه storage را پیش از آن؛ زیرا بازسازی previewها و transcodeها برای یک library بزرگ ساعت‌ها CPU مصرف می‌کند و از مسیر Administration > Jobs برای assetهای مفقود اجرا می‌شود. مواردی که هرگز نمی‌توانید حذف کنید library، upload و profile هستند؛ این directoryها تنها کپی هر original را نگه می‌دارند.

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

معمولاً بله، زیرا server در startup، migrationهای در انتظار را اعمال می‌کند و schema را به‌صورت مرحله‌ای جلو می‌برد. برعکس آن fail می‌شود: Immich از downgrade پشتیبانی نمی‌کند، حتی بین patch releaseها؛ بنابراین dump مربوط به release جدیدتر را نمی‌توان در server قدیمی‌تر load کرد. Restore را با IMMICH_VERSION که روی release ایجادکنندهٔ dump pin شده است انجام دهید، کامل‌بودن timeline را تأیید کنید و سپس upgrade کنید. release را در کنار هر dump با curl -fsS http://localhost:2283/api/server/version ثبت کنید، زیرا docker inspect فقط image reference پیکربندی‌شده را echo می‌کند و IMMICH_VERSION=v3 پیش‌فرض یک floating tag است که اطلاعاتی به شما نمی‌دهد.