كيفية نسخ مكدس Docker Compose احتياطياً وترقيته
تعرّف إلى ما يجب حفظه فعلياً: ملف Compose و.env والـvolumes وتفريغ قاعدة البيانات، ثم اختبر الاستعادة قبل تنفيذ pull والترقية.
ما يجب أن يتضمنه النسخ الاحتياطي لمكدس Docker Compose
يجب أن يتضمن النسخ الاحتياطي لمكدس Docker Compose أربعة عناصر منفصلة، ويعني فقدان أي عنصر منها أن التطبيق لن يعود للعمل: ملف Compose، و.env الموجود بجانبه، ومحتويات كل volume، وتفريغاً لقاعدة البيانات يُنشئه عميل قاعدة البيانات نفسه. لا يُعد نسخ ملفات قاعدة البيانات أثناء تشغيل الحاوية نسخة احتياطية. تستخدم الترقيات القائمة نفسها، مع قاعدة إضافية: أنشئ النسخة الاحتياطية قبل تنفيذ pull، لأن عمليات ترحيل المخطط مصممة للعمل إلى الأمام، ومعظم المشاريع لا توفر طريقة للعودة.
تفترض جميع الخطوات أدناه أن المكدس منشور بالفعل، وأن docker compose ps يعرضه قيد التشغيل. تستخدم الأمثلة مجلداً للمشروع في /srv/myapp، مع خدمتين تسميان app وdb. استبدل هذه الأسماء بأسماءك. تبقى الأوامر عامة عمداً، لأن العناصر المهمة، وهي volumes وقاعدة البيانات، تعمل بالطريقة نفسها مهما كان التطبيق.
حدّد ما يخزّنه مكدسك فعلياً
cd /srv/myapp
docker compose ps
docker compose config --volumes
docker volume ls --filter label=com.docker.compose.project=myappيطبع docker compose config --volumes الأسماء المختصرة للـvolumes المُسمّاة التي يعرّفها ملفك. ويطبع docker volume ls الأسماء الفعلية التي تحملها هذه الـvolumes على القرص. تختلف القائمتان لأن Compose يضع اسم المشروع في المقدمة: فالـvolume المكتوب بصيغة db_data في الملف يكون موجوداً باسم myapp_db_data. يكون اسم المشروع افتراضياً هو اسم الدليل، لذلك يؤدي تغيير اسم الدليل إلى توجيه المكدس إلى مجموعة جديدة من الـvolumes الفارغة، ويترك الـvolumes القديمة وفيها بياناتك كاملة. تحتاج كل الأوامر أدناه إلى الاسم الفعلي من docker volume ls.
لا تظهر bind mounts في أي من القائمتين. وهي في ملف Compose الإدخالات التي تحتوي على مسار للمضيف في الجانب الأيسر من النقطتين، مثل ./config:/app/config. هذه الإدخالات أدلة عادية على المضيف، لذلك يمكن للأدوات العادية الوصول إليها. توجد الـvolumes المُسمّاة تحت /var/lib/docker/volumes/، ويطبع docker volume inspect --format '{{.Mountpoint}}' myapp_db_data المسار الدقيق لإحداها. يغيّر نوع التخزين الذي يستخدمه مكدسك طريقة نسخه، وتشرح bind mounts مقابل الـvolumes المُسمّاة المفاضلة كاملة.
نظّم الآن ما وجدته في مجموعتين. تحتوي بعض الـvolumes على حالة لا يستطيع التطبيق إعادة إنشائها: الملفات المرفوعة، والمفاتيح المُنشأة، وقاعدة البيانات نفسها، وكل ما أدخله المستخدم في التطبيق. وتحتوي أخرى على بيانات مشتقة، مثل الصور المصغّرة وفهارس البحث، ويعيد التطبيق إنشاءها تلقائياً. لا يحقق نسخ المجموعة الثانية فائدة، بل يستهلك مساحة على القرص ويزيد وقت الاستعادة. يُعد volume ذاكرة Redis المؤقتة أوضح مثال: يؤدي فقدانه إلى إبطاء الطلب الأول فقط.
أنشئ نسخة احتياطية من ملف compose وملف .env
يوجد الملفان على المضيف بجوار بعضهما، ولا يوجد أيٌّ منهما داخل volume. يحتوي .env على كلمة مرور قاعدة البيانات، وسر التطبيق، وأي رموز API، لذلك فهو الملف الذي يحوّل مجموعة من volumes مجدداً إلى تطبيق عامل. ويُدرج هذا الملف عادةً أيضاً في .gitignore، ما يعني أن خطة «إعداداتي موجودة في git» تستبعد الملف الأهم. يُعد الاحتفاظ بالأسرار في ملف env النمط الصحيح، ويجعل ذلك النسخ الاحتياطي مسؤولية مماثلة.
sudo install -d -m 700 -o "$USER" -g "$(id -gn)" /srv/backups/myapp
cp -a compose.yaml .env /srv/backups/myapp/
chmod 600 /srv/backups/myapp/.envانسخ كل ملفات compose التي تستخدمها الحزمة، وليس الملف الأول فقط. تحتاج حزمة بدأت باستخدام -f compose.yaml -f compose.prod.yaml إلى استعادة الملفين بالطريقة نفسها، وتحدد كيفية دمج ملفات compose المتعددة القيم التي تصل فعلياً إلى الحاوية.
هناك تحذير يربط .env بالـvolumes. تقرأ صورة Postgres الرسمية POSTGRES_PASSWORD فقط عند تهيئة دليل بيانات فارغ. لا يؤدي تغيير هذه القيمة لاحقاً إلى تغيير كلمة المرور داخل قاعدة البيانات. إذا استعدت volume من الشهر الماضي بجوار .env الخاص باليوم، فسيفشل التطبيق في الاتصال باستخدام FATAL: password authentication failed for user "appuser"، مع أن الملفين يبدوان صحيحين عند الفحص. احتفظ بـ.env والـvolumes من اللحظة نفسها معاً في النسخة الاحتياطية نفسها.
تفريغ قاعدة البيانات باستخدام عميلها الخاص
يكتب خادم قاعدة البيانات في ملفاته باستمرار. يؤدي أخذ tar من /var/lib/postgresql/data أثناء تشغيل الخادم إلى نسخ بعض الصفحات من قبل عملية كتابة وبعضها من بعدها، لذلك يحتوي الأرشيف على مزيج من لحظات مختلفة قد يتعذر استعادتها. تقرأ أداة التفريغ داخل معاملة واحدة، لذلك يحتوي الملف على لحظة متسقة واحدة. هذا الفرق هو ما يميز النسخة الاحتياطية عن النسخة العادية.
docker compose exec -T db sh -c \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> /srv/backups/myapp/db-$(date +%F).dumpأبقِ على -T. فهو يعطّل تخصيص TTY، وعند إرفاق TTY يترجم Docker تدفق المخرجات في طريقه إلى الصدفة، ما يؤدي إلى إتلاف التفريغ الثنائي. لن تعرف ذلك حتى تفشل الاستعادة. علامات الاقتباس المفردة مهمة أيضاً: فهي تمنع صدفة المضيف من توسيع $POSTGRES_USER، وتتيح للصدفة داخل الحاوية توسيعه بدلاً من ذلك باستخدام القيم التي يضبطها ملف compose هناك مسبقاً. يكتب -Fc التنسيق المخصص، الذي يضغط البيانات أثناء الكتابة ويتيح لـpg_restore استخراج كائنات محددة منه لاحقاً.
توجد الأدوار وكلمات المرور الخاصة بها خارج نطاق أي قاعدة بيانات واحدة، لذا احفظها أيضاً:
docker compose exec -T db sh -c 'pg_dumpall -U "$POSTGRES_USER" --globals-only' \
> /srv/backups/myapp/globals.sqlتحقق بعد ذلك من أن الملف تفريغ وليس رسالة خطأ:
ls -lh /srv/backups/myapp/
head -c 5 /srv/backups/myapp/db-$(date +%F).dumpيبدأ التفريغ بالتنسيق المخصص بالبايتات الخمسة PGDMP. يشير الملف الذي حجمه صفر بايت، أو الذي يبدأ بـpg_dump:، إلى فشل الأمر. تنشئ الصدفة ملف المخرجات قبل تشغيل الأمر، لذلك يترك التفريغ الفاشل ملفاً باسم يبدو صحيحاً وطابع زمني يبدو صحيحاً. وهذا أكثر فشل صامت شيوعاً في النسخ الاحتياطي.
بالنسبة إلى MariaDB أو MySQL، يتغير العميل ولا تتغير البنية:
docker compose exec -T db sh -c \
'mariadb-dump -u root -p"$MARIADB_ROOT_PASSWORD" --single-transaction --databases "$MARIADB_DATABASE"' \
> /srv/backups/myapp/db-$(date +%F).sqlيوفّر --single-transaction تفريغاً متسقاً لجداول InnoDB من دون حظر عمليات الكتابة. في صورة MySQL يكون الأمر mysqldump، وتكون المتغيرات MYSQL_ROOT_PASSWORD وMYSQL_DATABASE. في صور MariaDB الحالية، ما يزال mysqldump يعمل اسماً توافقياً لـmariadb-dump. لاحظ أن كلمة المرور المقدمة في سطر الأوامر تظهر في قائمة عمليات الحاوية طوال مدة تشغيل التفريغ.
تحتاج SQLite إلى عناية خاصة. قاعدة البيانات عبارة عن ملف واحد، لكن المعاملات الحديثة قد تبقى في ملف -wal منفصل بجواره، لذلك يؤدي نسخ .db وحده إلى قاعدة بيانات تفتقد أحدث عمليات الكتابة. إذا كانت الصورة تتضمن العميل، يكتب sqlite3 /data/app.db ".backup '/data/app-backup.db'" نسخة متسقة أثناء تشغيل التطبيق. وإذا لم تتضمنه، فأوقف الحاوية وانسخ ملف .db مع الملفين المرافقين -wal و-shm.
إذا كانت قاعدة البيانات تعمل على المضيف بدلاً من داخل المكدس، فتنطبق الأوامر نفسها من دون السابقة docker compose exec، ومن المفيد قراءة تشغيل قاعدة البيانات في Docker أو على المضيف قبل إعادة البناء التالية.
التقاط وحدات التخزين
لا يملك named volume مساراً على المضيف ينبغي تعديله يدوياً، لذلك اربطه داخل حاوية مؤقتة وأنشئ الأرشيف منها.
docker run --rm \
-v myapp_uploads:/data:ro \
-v /srv/backups/myapp:/backup \
alpine:3 tar czf /backup/uploads.tar.gz -C /data .تربط الحاوية المساعدة وحدة التخزين للقراءة فقط في /data، ودليل النسخ الاحتياطي في /backup، ثم تكتب الأرشيف إلى جهة المضيف. يزيل --rm الحاوية المساعدة فور انتهاء tar. ويهم :ro لأن كتابة أمر tar بطريقة خاطئة لن تتمكن عندئذٍ من إتلاف المصدر. أما -C /data . فهو ما يجعل الاستعادة تضع الملفات في المكان الصحيح: إذ يخزّن كل مسار نسبةً إلى جذر وحدة التخزين. اكتب tar czf /backup/uploads.tar.gz /data بدلاً منه، فسيحصل كل مسار على data/ في بدايته، وعندها تنشئ الاستعادة /data/data داخل وحدة التخزين، ويرى التطبيق دليلاً فارغاً. يصبح الأرشيف مملوكاً لـroot لأن tar عمل بصفة root داخل الحاوية. شغّل sudo chown "$USER" /srv/backups/myapp/uploads.tar.gz إذا تسبب ذلك في مشكلة، واقرأ كيف يحدد PUID وPGID ملكية الملفات إذا عادت الملفات المستعادة غير قابلة للقراءة من جانب التطبيق.
شغّل ذلك مرة واحدة لكل named volume. لا تحتاج bind mounts إلى حاوية إطلاقاً: ينفّذ tar czf /srv/backups/myapp/config.tar.gz -C /srv/myapp/config . المهمة نفسها على المضيف.
حدّد لكل وحدة تخزين ما إذا كان يجب إيقاف التطبيق. يمكن لأمر tar يعمل على وحدة تخزين يعيد التطبيق الكتابة إليها مباشرةً أن يلتقط ملفاً أثناء الكتابة. بالنسبة إلى دليل uploads، حيث تُكتب الملفات مرة واحدة ثم تُقرأ فقط، يكون هذا الخطر محدوداً. أما في الحالات الأخرى، فأوقف تلك الخدمة طوال مدة النسخ باستخدام docker compose stop app، ثم docker compose start app. يُبقي stop الحاويات ووحدات التخزين في مكانها، وهذا هو المطلوب هنا تحديداً، ومن المفيد فهم الفرق بين down وstop قبل كتابة أيٍّ منهما.
لا تتعامل مع أرشيف tar لوحدة تخزين قاعدة البيانات على أنه نسخة احتياطية لقاعدة البيانات. التفريغ هو النسخة الاحتياطية. أما أرشيف وحدة تخزين لقاعدة بيانات متوقفة، فهو مسار سريع ومفيد لإعادة البناء، ولا شيء أكثر.
ترتيب العمليات
- انسخ ملفات Compose و
.envإلى دليل النسخ الاحتياطي. - أفرغ قاعدة البيانات بينما لا تزال قيد التشغيل.
- أوقف حاوية التطبيق إذا كانت وحداتها تتغير في مكانها.
- أنشئ أرشيفاً لكل وحدة مسماة ولكل دليل bind-mount.
- شغّل كل ما أوقفته، ثم أكّد ذلك باستخدام
docker compose ps. - دوّن وسوم الصور وملخصاتها التي تستخدمها المجموعة.
- انسخ دليل النسخ الاحتياطي بالكامل إلى خارج هذا الخادم.
الخطوة 7 هي التي يؤجلها الناس عادةً.
انقل النسخة خارج الخادم
تحميك النسخة الاحتياطية الموجودة على القرص نفسه الذي توجد عليه الحزمة من أخطائك فقط، ولا تحميك من أي شيء آخر. يؤدي تعطل وحدة تخزين واحدة، أو حذف خادم واحد، أو فقدان حساب واحد إلى فقدان النسختين في الوقت نفسه. ادفع المجلد إلى وحدة تخزين ليست على هذا الـVPS، وفق جدول زمني وبسياسة احتفاظ. يشرح نسخ restic الاحتياطية من VPS إعداد المستودع، وخيارات الاحتفاظ، وأمر التحقق، لذلك لا حاجة إلى تكرار أي منها هنا.
يمكن لـrestic أيضاً قراءة التفريغ مباشرة من pipe، ما يبقي قاعدة البيانات بنصها الصريح خارج القرص بالكامل:
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
| restic backup --stdin --stdin-filename db.dumpأيّاً كانت الأداة التي تستخدمها، ضع الجدول في مؤقت systemd أو مهمة cron، واجعل المهمة تُبلغ عن الفشل في مكان ستراه. يمكن لبرنامج نصي للنسخ الاحتياطي لا يذهب ناتجه إلى أي مكان أن يتوقف عن العمل لمدة ستة أشهر من دون أن يكتشف أحد ذلك.
أثبت عمل النسخة الاحتياطية باختبار استعادة
النسخة الاحتياطية التي لم يستعدها أحد هي مجرد افتراض. يوضح الاختبار أدناه كيفية الاستعادة إلى مكدس ثانٍ يعمل بجانب المكدس الأول، لذلك يواصل الإنتاج تقديم الخدمة، ولا يمكن لأي أمر تكتبه أن يصل إليه.
الآلية هي اسم المشروع. يستمد Compose الاسم من اسم الدليل، ويضيفه إلى كل حاوية ووحدة تخزين ينشئها. انسخ النسخة الاحتياطية إلى دليل جديد، وسيحصل المكدس المستعاد تلقائياً على وحدات التخزين الخاصة به.
sudo install -d -m 700 -o "$USER" -g "$(id -gn)" /srv/myapp-restore
cd /srv/myapp-restore
cp /srv/backups/myapp/compose.yaml /srv/backups/myapp/.env .حرّر ملف Compose المنسوخ بحيث لا يتعارض منفذ المضيف المنشور مع المكدس قيد التشغيل، وذلك باستخدام 18080:8080 بدلاً من 8080:8080، أو غيّر المتغير الذي يحدده في ملف .env المنسوخ. ثم أنشئ الحاويات ووحدات التخزين الفارغة الخاصة بها من دون تشغيل أي شيء:
docker compose create
docker volume ls --filter label=com.docker.compose.project=myapp-restoreيجب أن يعرض الأمر الثاني أسماء وحدات التخزين نفسها الموجودة في الإنتاج، مع myapp-restore_ في بدايتها. املأها، وشغّل قاعدة البيانات وحدها، وحمّل ملف التفريغ:
docker run --rm -v myapp-restore_uploads:/data -v /srv/backups/myapp:/backup \
alpine:3 tar xzf /backup/uploads.tar.gz -C /data
docker compose up -d db
docker compose exec -T db sh -c \
'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists' \
< /srv/backups/myapp/db-2026-08-16.dumpيحذف --clean --if-exists كل كائن قبل إعادة إنشائه، ما يجعل الاستعادة قابلة للتكرار. من دونه، يتوقف التشغيل الثاني عند قاعدة بيانات تحتوي على تلك الجداول مسبقاً، ويعرض pg_restore: error: could not execute query: ERROR: relation "users" already exists.
ثم شغّل بقية الخدمات واختبرها بالطريقة التي يستخدمها بها المستخدم:
docker compose up -d --wait
docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "\dt"'
docker compose logs --tail=50ينتظر docker compose up -d --wait حتى تعلن كل خدمة أنها قيد التشغيل أو سليمة، ويخرج برمز غير صفري إذا لم تصل إحدى الخدمات إلى ذلك، وهذا ما يجعل هذه الخطوة قابلة للبرمجة النصية. عندما لا تصبح إحدى الخدمات سليمة، يعرض docker compose ps حالتها، وتشرح فحوصات صحة Compose ما الذي يقرأه ذلك العمود. افتح التطبيق بعد ذلك على المنفذ البديل، وسجّل الدخول باستخدام حساب حقيقي. اكتب سجلاً واحداً، وافتح ملفاً واحداً موجوداً في وحدة تخزين. هذا الثنائي هو الدليل: تمت استعادة التفريغ، وتمت استعادة وحدة التخزين، وهما متوافقان. أما الاختبار الذي يثبت فقط ظهور صفحة تسجيل الدخول، فلا يثبت شيئاً عن بياناتك.
احذف مكونات الاختبار بعد نجاحه:
docker compose down -vهذا هو الموضع الوحيد الذي يكون فيه الخيار -v صحيحاً. في دليل الإنتاج، يحذف الأمر نفسه وحدات التخزين التي تحاول حمايتها.
كيفية ترقية مكدس Compose
اقرأ ملاحظات الإصدار لكل إصدار بين الإصدار الذي تشغّله والإصدار الذي تريده، وابحث فيها عن الكلمتين breaking وmigration. توضّح المشاريع التي لا تدعم الانتقال عبر عدة إصدارات رئيسية ذلك في ملاحظات الإصدار، بينما لا تخبرك عملية الترحيل التي ترفض التشغيل بالمشكلة إلا بعد أن تكون قد غيّرت جزءاً من المخطط.
سجّل ما تشغّله الآن قبل تغيير أي شيء:
docker compose images
docker image inspect --format '{{index .RepoDigests 0}}' postgres:16.4تعرض docker compose images الصورة والوسم اللذين تستخدمهما كل خدمة حالياً. ويُعدّ digest القيمة الوحيدة التي تحدد صورة بعينها بدقة، لأنّه يمكن نقل tag ليشير إلى مكان آخر في أي وقت.
خذ النسخة الاحتياطية من الأقسام السابقة وانسخها إلى خارج الخادم. افعل ذلك أيضاً عند تثبيت إصدار تصحيحي. الترقيات السهلة هي التي يتوقف الناس عن الاستعداد لها.
ثم ثبّت الإصدار في ملف Compose، لأنّ latest ليس إصداراً:
services:
db:
image: postgres:16.4باستخدام image: postgres:latest، يجلب docker compose pull ما يشير إليه ذلك الوسم اليوم، ولن تتمكن من تحديد ما كنت تشغّله أمس. يحوّل الوسم المثبّت الترقية إلى تعديل من سطر واحد يمكنك قراءته في git diff والتراجع عنه بتعديل آخر. ثبّت صورة التطبيق بالطريقة نفسها، مع أخذ الإصدار الدقيق من صفحة إصدارات المشروع.
اسحب الصور وأعد إنشاء الحاويات:
docker compose pull
docker compose up -d --waitيقارن docker compose up -d الملف بالحاويات قيد التشغيل، ويعيد إنشاء الخدمات التي تغيّرت صورتها أو إعداداتها فقط. ولا يلمس named volumes، لذلك تبدأ الحاوية الجديدة باستخدام البيانات الموجودة. هذه هي الغاية من العملية، وهي أيضاً مصدر الخطر، لأن بدء التشغيل الأول للإصدار الجديد هو الوقت الذي يعمل فيه عادةً ترحيل المخطط.
راقب ما يحدث:
docker compose ps
docker compose logs -f --tail=100 appتظهر الحاوية التي فشل تشغيلها بالحالة Exited (1) في عمود STATUS ضمن docker compose ps، ويظهر السبب في الأسطر الأخيرة من سجلها. تكون أخطاء الترحيل واضحة هناك وغير ظاهرة في أي مكان آخر. عندما تستقر السجلات، سجّل الدخول واستخدم التطبيق لمدة دقيقة.
إذا توقّف docker compose pull مع ظهور no space left on device، فعادةً ما تكون طبقات الصور القديمة هي السبب، ويستعيد حذف صور Docker غير المستخدمة المساحة. نفّذ الحذف بعد التأكد من نجاح الترقية، لا قبل ذلك، لأن هذه الطبقات القديمة هي التي يعتمد عليها التراجع السريع.
كيفية التراجع عند فشل الترقية
هناك حالتان، وتختلف كلفتهما كثيراً. إذا لم تغيّر الإصدار الجديد المخطط، فالتراجع يتطلب سطراً واحداً: أعد الوسم القديم إلى ملف Compose وشغّل docker compose up -d. يُستبدل الحاوي، وتبقى وحدات التخزين في مكانها، ويقرأ الكود القديم البيانات التي كتبها.
إذا رحّل الإصدار الجديد المخطط، فلن يعود الكود القديم قادراً على قراءته. تُكتب عمليات الترحيل للعمل إلى الأمام، ولا توفّر معظم المشاريع أي سكربت للتخفيض، لذلك تبدأ النسخة القديمة ثم تفشل عند أول استعلام إلى عمود أُعيدت تسميته أو حُذف، مع أخطاء من الشكل ERROR: column "avatar_url" does not exist. طريق العودة هو التفريغ الذي أنشأته قبل السحب: أعد الوسم القديم، وأزل وحدة تخزين قاعدة البيانات، وأعد إنشاءها فارغة، واستعد التفريغ فيها، ثم ابدأ التشغيل. من دون ذلك التفريغ، لا توجد أي طريقة للعودة، وهذا هو سبب إنشاء النسخة الاحتياطية قبل السحب.
تُعد الإصدارات الرئيسية من Postgres أوضح مثال على ذلك، وتفاجئ المستخدمين لأن الفشل يحدث أثناء الترقية، لا أثناء التراجع. يتغير التنسيق على القرص مع كل إصدار رئيسي. غيّر postgres:16.4 إلى postgres:17.2، وشغّل docker compose up -d، وسيرفض الخادم الجديد بدء التشغيل:
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.2.لا تنفّذ الصورة pg_upgrade نيابةً عنك. المسار المدعوم داخل مكدس Compose هو التفريغ والاستبدال والاستعادة: أنشئ التفريغ بينما لا يزال الإصدار القديم قيد التشغيل، ثم docker compose down، وأزل وحدة تخزين قاعدة البيانات، واضبط الوسم الجديد، ونفّذ docker compose create لإنشاء مجلد بيانات فارغ جديد، ثم ابدأ قاعدة البيانات، واستعد التفريغ، وشغّل بقية الخدمات. احتفظ بالتفريغ القديم حتى يتعامل الإصدار الرئيسي الجديد مع حركة مرور فعلية لمدة يوم. لا تحتاج الترقية بين الإصدارات الفرعية ضمن الإصدار الرئيسي نفسه، مثل 16.4 إلى 16.9، إلى أي من ذلك، لأن التنسيق مستقر بينها ويبدأ الحاوي ببساطة.
هل تُعد لقطات VPS الاحتياطية نسخة احتياطية؟
هي مكمّلة للنسخ الاحتياطية، ويفشل كل منهما بطريقة مختلفة. تنسخ اللقطة القرص بأكمله على مستوى hypervisor، ولذلك تعيد الجهاز بالكامل خلال دقائق، بما في ذلك الأجزاء التي نسيت نسخها احتياطياً. وهذا يجعلها الأداة المناسبة لمهمة محددة: تسبّب الترقية في تعطّل الخادم، وتريد إعادته إلى حالته قبل عشرين دقيقة.
لكنها أداة ضعيفة لكل ما عدا ذلك. فمستوى التفاصيل هو الجهاز بأكمله، لذلك يتطلب استرداد جدول محذوف استعادة خادم كامل في مكان ما، ثم استخراج الجدول منه. وتكون مدة الاحتفاظ عادةً قصيرة. كما توجد النسخ عادةً في حساب المزوّد نفسه الذي يوجد فيه الخادم، ولذلك يؤدي فقدان الحساب إلى فقدان الخادم ولقطاته معاً. وتلتقط لقطة الجهاز قيد التشغيل قاعدة البيانات أثناء عملية كتابة، فتجري قاعدة البيانات استرداداً بعد التعطل عند بدء تشغيلها للمرة الأولى، وتُفقد أي معاملة كانت لا تزال قيد التنفيذ.
استخدم كليهما. اللقطة هي زر التراجع أثناء فترة الترقية. أما التفريغ فهو النسخة التي تبقى بعد حذف الحساب. يوضّح الفرق بين اللقطات والنسخ الاحتياطية حالات الفشل التي يغطيها كل منهما فعلياً. وهذا الدليل نفسه للنسخ الاحتياطية يحوّل نقل مجموعة خدمات إلى VPS جديد إلى مهمة روتينية بدلاً من إعادة بنائها اعتماداً على الذاكرة.
ما الذي يحدث خطأً، وما الذي ستراه
خيار volumes مع down. يزيل docker compose down -v وحدات التخزين المسماة التي يعرّفها الملف، ويؤكد Compose ذلك بسطر يحتوي على Volume myapp_db_data Removed. لا يمكن التراجع عن ذلك. يترك docker compose down العادي هذه الوحدات دون تغيير. اكتب الصيغة الطويلة، docker compose down --volumes، حتى يكون الخيار المدمّر كلمةً يجب عليك كتابتها بالكامل.
نسخة تفريغ بلا السلسلة السحرية. يعني pg_restore: error: did not find magic string in file header أن الملف ليس أرشيفاً. والسبب المعتاد هو غياب -T في docker compose exec، لأن وجود TTY يجعل التدفق يُترجم في طريقه إلى shell، فتصل نسخة التفريغ الثنائية تالفة. أعد إنشاء نسخة التفريغ باستخدام -T، ثم افحص أول خمسة بايتات باستخدام head -c 5.
كلمة مرور لا تتغير. يعني FATAL: password authentication failed for user "appuser" بعد الاستعادة أن .env ودليل البيانات يعودان إلى لحظتين مختلفتين. يضبط image كلمة المرور عند إنشاء دليل بيانات فارغ فقط، لذلك لا يغيّر تعديل .env لاحقاً شيئاً داخل قاعدة البيانات. استعد .env المطابق، أو غيّر كلمة المرور داخل قاعدة البيانات باستخدام ALTER USER.
وحدة تخزين ثانية وفارغة. ينشئ Docker وحدة تخزين عند الطلب، لذلك يكتب docker run -v myapp_upload:/data، مع غياب s، في وحدة تخزين جديدة وفارغة تماماً، ثم يبلغ عن نجاح العملية. يعرض docker volume ls الاسمين معاً، ويكون أحدهما بلا محتوى. انسخ أسماء وحدات التخزين من docker volume ls بدلاً من كتابتها اعتماداً على الذاكرة.
استعادة موجّهة إلى بيئة الإنتاج. يؤدي تشغيل أوامر الاستعادة في /srv/myapp بدلاً من /srv/myapp-restore إلى استبدال البيانات الحية بالنسخة الاحتياطية، وتبدو الأوامر متطابقة في المكانين. تحقّق من pwd قبل كل أمر استعادة، وأبقِ الاختبار في دليله الخاص.
FAQ
هل يحذف docker compose down بياناتي؟
لا. يزيل docker compose down الحاويات والشبكة الافتراضية، ويترك وحدات التخزين المسماة وعمليات الربط bind mounts دون تغيير. يزيل docker compose down -v وحدات التخزين المسماة التي يعرّفها ملفك، وهذا إجراء نهائي. عمليات الربط bind mounts هي أدلة على المضيف، لذلك لا يزيلها Compose أبداً. إذا أردت إيقاف الخدمات أثناء النسخ الاحتياطي مع ترك كل شيء آخر دون تغيير، فاستخدم docker compose stop بدلاً من ذلك.
هل يمكنني نسخ دليل بيانات Postgres بدلاً من تشغيل pg_dump؟
فقط بعد إيقاف الحاوية. أثناء تشغيل الخادم، تتغير ملفاته أثناء النسخ، وقد تحتوي النسخة على مزيج من لحظات مختلفة لا يمكن استعادته بشكل صحيح. كما يرتبط النسخ على مستوى الملفات بإصدار رئيسي واحد من Postgres، لذلك لن يبدأ تحت إصدار رئيسي مختلف. أوقف الحاوية، وأرشف وحدة التخزين، وشغّلها مجدداً، وتعامل مع النتيجة كمسار سريع لإعادة البناء، لا كنسختك الاحتياطية الوحيدة. ملف التفريغ هو النسخة القابلة للنقل، وهو الملف الذي تستعيد منه.
كيف أرقّي Postgres إلى إصدار رئيسي جديد في Compose؟
لا يكفي تغيير الوسم. يرفض الخادم الجديد البدء باستخدام دليل البيانات القديم، ويسجل The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.2. شغّل pg_dump مع استمرار تشغيل الإصدار القديم، ثم docker compose down، وأزل وحدة تخزين قاعدة البيانات، واضبط الوسم الجديد، وشغّل docker compose create لإنشاء وحدة تخزين فارغة جديدة، ثم شغّل قاعدة البيانات واستعد ملف التفريغ إليها. احتفظ بملف التفريغ القديم حتى يتعامل الإصدار الجديد مع حركة مرور فعلية.
كم مرة ينبغي تشغيل النسخ الاحتياطي، وكم من الوقت ينبغي الاحتفاظ به؟
اربط الفاصل الزمني بكمية العمل التي تقبل إعادة تنفيذها. يناسب النسخ الليلي حزمة شخصية أو حزمة لفريق صغير، مع نسخة احتياطية يدوية إضافية مباشرة قبل أي ترقية. بالنسبة إلى مدة الاحتفاظ، احتفظ بتاريخ يكفي لتغطية الضرر الذي لم تلاحظه فوراً، لأن اكتشاف جدول تالف يوم الجمعة لا تعالجه نسخة ليلة الخميس. تُعد restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune سياسة بداية معقولة. أياً كان الجدول الزمني، استعد منها مرة كل ربع سنة. إلى أن تفعل ذلك، ليست لديك نسخ احتياطية، بل ملفات.
هل يجب أن أوقف الحزمة بأكملها لإنشاء نسخة احتياطية؟
عادةً لا. يكون تفريغ قاعدة البيانات متسقاً أثناء تشغيل الخادم، لذلك لا تحتاج قاعدة البيانات إلى توقف. السؤال الحقيقي يتعلق بوحدات التخزين. إذا كان التطبيق يضيف ملفات فقط، مثل دليل uploads، فالأرشفة أثناء التشغيل آمنة بما يكفي. أما إذا كان يعيد كتابة الملفات في مكانها، فأوقف تلك الخدمة وحدها طوال مدة النسخ باستخدام docker compose stop app، ثم شغّلها مجدداً بعد ذلك. يكون إيقاف التطبيق مع استمرار تشغيل قاعدة البيانات عادةً أقصر نافذة آمنة يمكنك ترتيبها.