SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor

تشغيل قواعد البيانات في Docker أم على الخادم المباشر؟

هل تشغيل PostgreSQL أو MySQL أو Redis داخل حاوية Docker آمن للإنتاج؟ اكتشف المخاطر الحقيقية المتعلقة بإدارة المجلدات والنسخ الاحتياطي وترقيات الإصدارات لتجنب فقدان البيانات.

هل يجب تشغيل قاعدة البيانات داخل Docker أم على الخادم المضيف؟

شغّل قاعدة البيانات داخل Docker. بالنسبة لمكدس تطبيقات واحد على خادم VPS واحد، يُعد استخدام PostgreSQL أو MySQL أو MongoDB أو Redis في حاوية خياراً طبيعياً في بيئات الإنتاج، والجدل الدائر حول هذا الموضوع غالباً ما يركز على نقاط غير جوهرية. الحاوية هي عملية Linux معزولة بـ namespaces وcgroups، وليست جهازاً افتراضياً، لذا لا يوجد hypervisor بين قاعدة البيانات والقرص. باستخدام bind mount أو volume محلي مسمى، تصل عمليات القراءة والكتابة مباشرة إلى نظام ملفات المضيف، وهو نفس النظام الذي سيستخدمه تثبيت الحزمة التقليدي.

التكلفة الحقيقية هي تكلفة تشغيلية. هناك أربعة عوامل تحدد ما إذا كان هذا الإعداد جيداً أم كارثة بطيئة: مكان تخزين البيانات، ومن يملك ذلك المجلد، وكيفية إجراء ترقية الإصدار الرئيسي، وما إذا كنت قد اختبرت استعادة نسخة احتياطية من قبل. إذا أتقنت هذه الجوانب، تصبح الحاوية مجرد تفصيل تقني. وإذا أخطأت فيها، ستصبح الحاوية هي السبب الذي ستلومه.

هذا القرار ينطبق على أي قاعدة بيانات خادم. تستخدم الأمثلة أدناه PostgreSQL وMySQL وMongoDB وRedis، وتتم الإشارة إلى الاختلافات الخاصة بكل منتج حيثما كان ذلك مهماً.

ما الذي يغيّره الحاوية فعلياً

لا يتغير مسار التخزين، طالما أنك تقوم بعملية mount له. النواة هي نفسها، وذاكرة التخزين المؤقت للصفحات (page cache) هي نفسها، ونظام الملفات هو نفسه.

هناك فخ حقيقي واحد في الأداء، وهو الحالة التي لا تقوم فيها بعمل mount لأي شيء. بدون volume، يذهب دليل البيانات إلى الطبقة القابلة للكتابة في الحاوية، وهي عبارة عن نظام ملفات overlay مكدس فوق الصورة. تكون عمليات الكتابة هناك أبطأ، وتُحذف الطبقة بأكملها عند إزالة الحاوية. هذا هو سبب حدوث مشكلة "قاعدة بياناتي كانت فارغة هذا الصباح".

ما يتغير حقاً:

  • دورة الحياة. يؤدي docker compose down إلى تدمير الحاوية. أي شيء لم يكن موجوداً في volume يذهب معها.
  • الإصدار. وسم الصورة (image tag) هو الإصدار. لا يوجد apt upgrade داخل حاوية قاعدة البيانات ينجو من عملية docker compose pull التالية.
  • محاسبة الذاكرة. حد الـ cgroup هو حاجز صارم تفرضه النواة، ولا تعلم قاعدة البيانات بوجوده.
  • المستخدم. تعمل العملية بمعرف مستخدم رقمي (numeric user id) داخل الحاوية، والذي قد لا يملك أي شيء على مضيفك (host).

موقع البيانات يحدد كل شيء

هناك خياران جيدان وخطأ شائع واحد.

  • وحدة تخزين مسماة (Named volume): pgdata:/var/lib/postgresql/data. ينشئ Docker الدليل في /var/lib/docker/volumes/<project>_pgdata/_data، ويقوم نقطة دخول الصورة (image entrypoint) بتعيين الملكية عند التشغيل الأول. هذه هي الإجابة الافتراضية.
  • ربط المجلدات (Bind mount): /srv/appname/pg:/var/lib/postgresql/data. أنت تختار المسار، لذا فأنت المسؤول عن حل مشكلات الصلاحيات.
  • عدم استخدام أي وحدة تخزين. انظر أعلاه. البيانات تبقى داخل الحاوية.

المفاضلة الكاملة موضوع مستقل، ويغطي ربط المجلدات مقابل وحدات التخزين المسماة تفاصيله. بالنسبة لقاعدة البيانات، الخلاصة هي: استخدم وحدة تخزين مسماة ما لم يكن لديك سبب محدد لمعرفة مسار المضيف، وإذا استخدمت ربط المجلدات، ضعه في مكان مستقر مثل /srv/appname/pg بدلاً من وضعه داخل دليل المشروع حيث يمكن لـ git clean الوصول إليه.

قيد صارم واحد: لا تضع دليل بيانات قاعدة البيانات على NFS (نظام ملفات الشبكة) أو أي وحدة تخزين شبكية لم تختبر سلوك القفل و fsync الخاص بها. تفترض قواعد البيانات أن نجاح fsync يعني أن البايتات أصبحت على وحدة تخزين مستقرة. عندما يكون هذا الافتراض خاطئاً، ستحصل على تلف في البيانات يظهر بعد أسابيع.

ثبّت اسم المجلد (Volume) قبل أن يضيع

يُسمّي Compose المجلد باستخدام <project>_<volume>، ويعتمد اسم المشروع افتراضياً على اسم المجلد الذي يحتوي الملف. لذا، تعتمد هوية المجلد على اسم المجلد، وهو أمر يغيره المستخدمون دون تفكير.

إذا نقلت /srv/app إلى /srv/app-old، أو أعدت تسمية مفتاح pgdata في ملف compose، فإن أمر docker compose up -d التالي سينشئ مجلداً جديداً وفارغاً. سيقوم Postgres بتهيئة قاعدة بيانات جديدة فيه. ستعمل الحاوية بشكل سليم، وسيبدأ التطبيق، لكن جميع الجداول ستكون مفقودة. الخبر السار هو أن المجلد القديم لا يزال موجوداً على القرص بالاسم القديم.

docker volume ls
docker volume inspect app_pgdata

ثبّت الأسماء لمنع حدوث ذلك. حدد اسم المشروع واسم المجلد بشكل صريح:

name: myapp

services:
  db:
    image: postgres:17
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
    name: myapp_pgdata

إذا كان هناك مجلد مهجور يحتوي على بياناتك، انسخ محتوياته بعد إيقاف قاعدة البيانات:

docker compose stop db
docker run --rm -v app_pgdata:/from -v myapp_pgdata:/to alpine sh -c 'cp -a /from/. /to/'
docker compose start db

لا تنسخ البيانات أثناء عمل قاعدة البيانات، لأنك ستحصل على نسخة تالفة من الملفات التي كانت قيد الكتابة. أوقفها أولاً.

من يملك دليل البيانات

تُشغّل صور Postgres وMySQL وMongoDB الرسمية خادمها باستخدام معرّف مستخدم غير متميز، وعادة ما يكون 999. عندما يبدأ الحاوية بصلاحيات root، يقوم نقطة الدخول (entrypoint) بتغيير ملكية دليل البيانات إلى ذلك المستخدم ثم يتخلى عن الصلاحيات. لهذا السبب يعمل الربط (bind mount) الفارغ عادةً من المحاولة الأولى.

يتعطل هذا الأمر بمجرد تعيين user: في ملف compose، لأن نقطة الدخول لا تملك حينها أي صلاحيات لإصلاح أي شيء. يذكر Postgres ذلك مباشرة:

initdb: error: could not change permissions of directory "/var/lib/postgresql/data": Operation not permitted

يؤدي وجود دليل بيانات بصلاحيات خاطئة إلى ظهور رسالة مختلفة، وهي رسالة تستحق المعرفة لأن الحل هو chmod وليس chown:

FATAL:  data directory "/var/lib/postgresql/data" has invalid permissions
DETAIL:  Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).

يفشل MongoDB عند استخدام bind mount مملوك لـ root بسبب ملف القفل (lock file):

Unable to create/open the lock file: /data/db/mongod.lock (Permission denied). Ensure the user executing mongod is the owner of the lock file and has the appropriate permissions.

الحل هو تنفيذ chown لدليل المضيف باستخدام المعرّف الرقمي، وليس الاسم:

sudo chown -R 999:999 /srv/appname/pg
sudo chmod 700 /srv/appname/pg
ls -ldn /srv/appname/pg

يطبع ls -ldn الأرقام بدلاً من الأسماء، ويجب أن يظهر 999 999. الحساب المسمى postgres على مضيفك والحساب المسمى postgres داخل الصورة غير مترابطين: يقارن النواة الأرقام، بينما يتم البحث عن الأسماء بشكل منفصل على كل جانب. يشرح كيفية تعيين PUID وPGID لمستخدمي المضيف داخل الحاوية هذا التعيين بشكل صحيح. في حالة Docker بدون root أو إعادة تعيين مساحة اسم المستخدم (user namespace remapping)، تتغير الأرقام مرة أخرى، لذا اقرأ المعرّفات من الحاوية قيد التشغيل بدلاً من افتراض الرقم 999.

تُنهي المجلدات المسماة (named volumes) هذا القسم بالكامل عند التشغيل الأول، لأن Docker ينشئ دليلاً فارغاً وتصبح ملكيته لنقطة الدخول.

الترقيات: ترقية الحزمة مقابل تغيير وسم الصورة

على المضيف، ينقلك apt upgrade عبر إصدار فرعي. لن يقوم توزيعك بتغيير إصدار قاعدة البيانات الرئيسي تلقائياً، وعندما تختار الترقية، يمكن تثبيت مجموعتي الملفات الثنائية معاً، وهو بالضبط ما يحتاجه pg_upgrade.

في الحاوية، الوسم هو الإصدار، لذا فإن الترقية تعني تعديل سطر واحد. هذا يجعل الترقيات الفرعية بسيطة والترقيات الرئيسية إجراءً منظماً.

غيّر postgres:16 إلى postgres:17، وشغّل docker compose up -d، وستتوقف الحاوية فوراً:

PostgreSQL Database directory appears to contain a database; Skipping initialization
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.

لا يوجد أي ضرر. ترفض الملفات الثنائية الجديدة قراءة تخطيط الكتالوج القديم الموجود على القرص، والذي يتغير بين الإصدارات الرئيسية. أعد الوسم إلى postgres:16 وستعمل الحاوية مجدداً. هذا التراجع هو الميزة الحقيقية الوحيدة التي توفرها الحاويات عند الترقية.

المسار المدعوم هو التفريغ والاستعادة (dump and restore). تفضّل PostgreSQL أن يتم التفريغ بواسطة العميل الأحدث، لذا شغّله من الصورة الجديدة مقابل الخادم القديم الذي لا يزال يعمل على شبكة compose:

docker run --rm --network myapp_default -e PGPASSWORD="$POSTGRES_PASSWORD" \
  postgres:17 pg_dumpall -h db -U postgres > /srv/backups/all.sql
ls -lh /srv/backups/all.sql
tail -n 2 /srv/backups/all.sql

يجب أن يكون حجم الملف عشرات الكيلوبايتات على الأقل وأن ينتهي بسطر يقرأ PostgreSQL database cluster dump complete. الملف الذي يبلغ حجمه بضع مئات من البايتات يعني أن التفريغ فشل وأنك على وشك حذف وحدة تخزين (volume) دون داعٍ. فقط بعد هذا التحقق:

docker compose down
docker volume rm myapp_pgdata
# edit the compose file: image: postgres:17
docker compose up -d db
docker compose exec -T db psql -U postgres -f /dev/stdin < /srv/backups/all.sql

تختلف المحركات الأخرى:

  • يقوم MySQL 8 بترقية قاموس البيانات الخاص به عند بدء التشغيل، لذا فإن تغيير الوسم الفرعي عادة ما يكون مجرد إعادة تشغيل. اقرأ ملاحظات الإصدار قبل القفز بين سلاسل الإصدارات، وقم بإجراء تفريغ (dump) أولاً في كلتا الحالتين.
  • تتوقع MariaDB تشغيل mariadb-upgrade بعد صعود الخادم على الإصدار الجديد.
  • يجب ترقية MongoDB إصداراً رئيسياً واحداً في كل مرة، وبعد كل خطوة تقوم بتعيين إصدار توافق الميزات قبل المتابعة. تخطي إصدار يعني أن mongod سيرفض البدء ويسجل سطراً في السجلات UPGRADE PROBLEM يذكر featureCompatibilityVersion. بدءاً من MongoDB 7.0 فصاعداً، يحتاج الأمر إلى علامة تأكيد صريحة: db.adminCommand({ setFeatureCompatibilityVersion: "8.0", confirm: true }).
  • يقوم Redis بتحميل ملفات اللقطات (snapshot) القديمة بسلاسة ولكن ليس الأحدث، لذا فإن الترقية هي إعادة تشغيل، وقد يفشل التراجع في تحميل البيانات.

القاعدة العامة: تجعل الحاوية التراجع سهلاً، لكنها لا تجعل الترقية أسهل.

لماذا يتوقف حاوية قاعدة البيانات الخاصة بي برمز الخروج 137؟

السبب هو أن أداة OOM killer (قاتل العمليات عند نفاد الذاكرة) في النواة قامت بإنهاء الحاوية. الرمز 137 هو ناتج جمع 128 مع الإشارة 9.

docker compose ps
docker inspect myapp-db-1 | grep -i oomkilled
journalctl -k | tail -n 20

يُظهر docker compose ps القيمة Exited (137)، ويقرأ سطر الفحص "OOMKilled": true، ويحتوي سجل النواة على إدخال مطابق:

Memory cgroup out of memory: Killed process 4711 (postgres) total-vm:2170416kB

إليك الآلية التي تحدث، وهي تفاجئ الكثيرين. تقوم قواعد بيانات PostgreSQL وMySQL بتحديد حجم مخازنها المؤقتة بناءً على إجمالي الذاكرة التي يبلغ عنها المضيف. لا يغير حد cgroup هذا الرقم بالنسبة لها. على مضيف بذاكرة 16 GB وحد 2 GB، تخطط قاعدة البيانات كما لو كانت تمتلك 16 GB، فيقوم cgroup بإنهاؤها قبل وقت طويل من تعرض المضيف لأي ضغط. لذا، فإن حد الذاكرة وحده لا يكفي. يجب عليك أيضاً إخبار قاعدة البيانات بما تمتلكه فعلياً:

  • PostgreSQL: اضبط shared_buffers، وانتبه إلى work_mem. يتم تخصيص work_mem لكل عملية فرز لكل اتصال، لذا فإن القيمة السخية مضروبة في خمسين اتصالاً هي السبب المعتاد لموت الحاوية تحت الضغط بدلاً من موتها عند بدء التشغيل.
  • MySQL وMariaDB: اضبط innodb_buffer_pool_size، والذي يبلغ افتراضياً 128M. اترك innodb_dedicated_server معطلاً داخل الحاوية، لأن وظيفته الكاملة هي تحديد حجمه بناءً على ذاكرة الجهاز المكتشفة.
  • MongoDB: اضبط حجم ذاكرة التخزين المؤقت WiredTiger بشكل صريح بدلاً من تركه يخمن بناءً على ذاكرة المضيف.
  • Redis: القيمة maxmemory افتراضياً غير محدودة، لذا ينمو Redis حتى يوقفه cgroup. اضبط maxmemory بشكل مريح تحت حد الحاوية واختر maxmemory-policy مناسباً.

يُبلغ Postgres أيضاً عن الحدث من جانبه، وهذا الزوج من الأسطر هو ما ستجده في السجل:

LOG:  server process (PID 123) was terminated by signal 9: Killed
LOG:  terminating any other active sessions due to crash of another server process

إنهاء عملية خلفية واحدة يجبر جميع العمليات الأخرى على إعادة التشغيل، لأن الذاكرة المشتركة قد تصبح غير متسقة. هذا يسبب عاصفة اتصالات لتطبيقك، وليس حدثاً هادئاً. يغطي الرابط ضبط حدود الذاكرة في Docker Compose الصيغة والفرق بين mem_limit وصيغة deploy.resources.

لا تختفي هذه المشكلة على المضيف، بل تنتقل. بدون cgroup، تتنافس قاعدة البيانات مع كل شيء آخر على الجهاز، ويختار قاتل OOM الخاص بالمضيف ضحية بناءً على النتيجة، والتي قد تكون sshd. الحد الذي ينهي قاعدة البيانات بشكل متوقع أسهل في الإدارة من قاتل OOM الخاص بالمضيف الذي قد يمنعك من الدخول.

النسخ الاحتياطي: استخرج البيانات داخلياً، واحفظها خارجياً

لا تنسخ قاعدة بيانات قيد التشغيل عبر نسخ مجلد بياناتها مباشرة. النسخ على مستوى الملفات أثناء كتابة الخادم للبيانات يؤدي إلى ملفات تالفة، ولن تكتشف ذلك إلا عند محاولة الاستعادة.

هناك طريقتان سليمتان: استخراج البيانات (dump) باستخدام أداة قاعدة البيانات الأصلية أثناء عملها ثم نسخ ملف الاستخراج، أو إيقاف الحاوية ونسخ المجلد وهي في حالة توقف تام.

docker compose exec -T db pg_dump -U postgres -Fc appdb > /srv/backups/appdb.dump
docker compose exec -T db mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --single-transaction --all-databases > /srv/backups/mysql.sql
docker compose exec -T db mongodump --archive --gzip --db appdb > /srv/backups/appdb.archive.gz
docker compose exec -T redis redis-cli BGSAVE

يعد -T أمراً جوهرياً. بدونه، قد يقوم docker compose exec بربط طرفية (terminal) بالأمر، وتضيف طبقة الطرفية رموز إرجاع السطر (carriage returns) إلى تدفق المخرجات. سيؤدي ذلك إلى استعادة ملف نصي بأخطاء غريبة، بينما سيتلف ملف البيانات الثنائي (binary dump) تماماً. تفشل هذه العملية بصمت أثناء النسخ، وتظهر نتائجها الكارثية بعد شهر عند الحاجة إليها.

يمنحك --single-transaction لقطة متسقة لجداول InnoDB عبر mysqldump دون الحاجة إلى قفل الخادم بالكامل.

تنتج هذه الأوامر ملفاً واحداً لكل منها. هذه ليست نظام نسخ احتياطي: لا توجد سياسة استبقاء، ولا نسخة خارج الخادم، ولا عملية تحقق. سلّم مجلد النسخ الاحتياطي إلى أداة توفر هذه الخصائص الثلاث، وهو ما يوضحه restic backups from a VPS. انسخ /srv/backups، وليس /var/lib/docker/volumes.

ثم قم بإجراء عملية الاستعادة، لأن النسخة الاحتياطية التي لم تجرب استعادتها قط ليست نسخة احتياطية:

docker compose exec -T db createdb -U postgres restore_test
docker compose exec -T db pg_restore -U postgres -d restore_test < /srv/backups/appdb.dump
docker compose exec -T db psql -U postgres -d restore_test -c '\dt'

يجب أن يعرض \dt جداول تطبيقك. النتيجة الفارغة، أو ظهور Did not find any relations.، يعني أن ملف الاستخراج ليس كما تتوقع. احذف restore_test عند الانتهاء.

الأمر الذي يحذف كل شيء

docker compose down -v.

الأمر down البسيط يزيل الحاويات والشبكة. أما -v فيزيل أيضاً كل وحدة تخزين (volume) مُسمّاة مُعرّفة في ملف compose هذا، بالإضافة إلى كل وحدة تخزين مجهولة مرتبطة بتلك الحاويات. لا توجد مطالبة تأكيد ولا إمكانية للتراجع. هذه هي الطريقة الأكثر شيوعاً لتدمير قاعدة بيانات ذاتية الاستضافة، وعادة ما تحدث أثناء استكشاف أخطاء شيء غير ذي صلة، لأن إجابة في منتدى نصحت بتنفيذه.

هناك أربعة أمور تقلل من نطاق الضرر:

  • عرّف وحدة تخزين قاعدة البيانات كـ external: true. لن يقوم Compose بإزالة وحدة تخزين لا يملكها، لذا لا يمكن لـ -v الوصول إليها. أنت تنشئها مرة واحدة باستخدام docker volume create myapp_pgdata.
  • استخدم docker compose stop و docker compose start لعمليات إعادة التشغيل الروتينية. يشرح الرابط down مقابل stop في Compose ما يزيله كل منهما.
  • احتفظ بنسخ احتياطية (dumps) على مسار في المضيف خارج أي وحدة تخزين يديرها compose.
  • لا تنسخ أبداً -v من إجابة لاستكشاف الأخطاء وإصلاحها إلى حزمة (stack) تحتوي على بيانات تهتم بها.

لا تنشر منفذ قاعدة البيانات

هذا السطر يضع قاعدة بياناتك على الإنترنت العام:

    ports:
      - "5432:5432"

فهو يربط الخدمة بجميع الواجهات. يقوم Docker بنشر المنفذ عبر إعادة كتابة وجهة الحزمة قبل أن تصل إلى قواعد الإدخال في جدار الحماية الخاص بك، وبما أن قواعد ufw تقع في سلسلة الإدخال (input chain)، فإن ufw deny 5432 لا تؤدي أي وظيفة على الإطلاق. يوضح سبب تجاوز المنافذ المنشورة عبر Docker لجدار الحماية ufw مسار عبور السلسلة.

يصل التطبيق الموجود في نفس مشروع compose إلى قاعدة البيانات عبر اسم الخدمة في شبكة compose، لذا فهو لا يحتاج إلى منفذ منشور. احذف هذا الجزء. إذا كنت تريد الوصول إلى قاعدة البيانات من المضيف (host)، اربطها بـ loopback فقط:

    ports:
      - "127.0.0.1:5432:5432"

تحقق مما يستمع فعلياً على المنافذ:

sudo ss -ltnp | grep 5432

127.0.0.1:5432 هو ما تريده. أما 0.0.0.0:5432 فيعني أن أي شخص يمكنه محاولة تخمين كلمة مرورك.

ما الذي يجب تشغيله وأين

تطبيق واحد على خادم VPS واحد. استخدم حاوية (Container). خصص وحدة تخزين (Volume) باسم ثابت، ولا تنشر المنفذ (Port) للعامة، وحدد سقفاً للذاكرة يتناسب مع إعدادات قاعدة البيانات، وقم بإجراء نسخة احتياطية يومية إلى مسار على المضيف ليقوم restic بجمعها. ابدأ من تثبيت Docker نظيف على خادم VPS واحتفظ بمكدس التقنيات في ملف compose واحد تقوم برفعه إلى مستودع git. الفائدة ملموسة: يصبح إصدار قاعدة البيانات سطراً قابلاً للمراجعة في git.

مضيف يشغّل عدة خدمات. استخدم حاويات، وقاعدة بيانات واحدة لكل تطبيق، بدلاً من خادم مشترك للجميع. الخادم المشترك يربط كل تطبيق بجدول تحديث واحد، وقد يؤدي استعلام واحد خارج عن السيطرة إلى توقف جميع الخدمات. امنح كل حاوية سقفاً خاصاً للذاكرة بحيث ينحصر تأثير الاستعلام السيئ في التطبيق الذي تسبب به. استهلاك مساحة قرص إضافية قليلة لعدة نسخ صغيرة من Postgres أفضل بكثير من تعقيدات التنسيق.

قاعدة البيانات هي المنتج الأساسي. شغّلها على المضيف مباشرة من مستودع حزم المورّد، أو اشترك في خدمة مدارة. يحتاج pg_upgrade إلى تثبيت إصدارين رئيسيين من الملفات التنفيذية في وقت واحد، وهو ما توفره الحزم ولا توفره صورة (Image) ذات إصدار واحد. النسخ المتماثل والاستعادة إلى نقطة زمنية محددة عبر أرشفة WAL (سجل الكتابة المسبقة) أسهل عندما تسيطر قاعدة البيانات على الجهاز وأقراصه. اختر المسار الأكثر استقراراً وموثوقية للنظام الذي قد يوقظك من نومك في الساعة 03:00.

التطبيق صغير الحجم. فكّر في عدم تشغيل قاعدة بيانات خادم على الإطلاق. بالنسبة لتطبيق ويب بكاتب واحد على خادم VPS واحد، غالباً ما يكون استخدام SQLite في بيئة الإنتاج على خادم VPS خياراً أفضل، حيث تكون النسخة الاحتياطية ملفاً واحداً، ومسار الترقية هو مجرد تحديث لإصدار المكتبة.

FAQ

هل من الآمن تشغيل قاعدة بيانات إنتاجية داخل Docker؟

نعم، بالنسبة لمكدس تطبيقات على خادم واحد. الحاوية هي عملية Linux محاطة بـ namespaces وcgroups، لذا عند ربط volume، تكتب قاعدة البيانات في نظام ملفات المضيف نفسه الذي كانت ستستخدمه لو ثبّتها من حزمة برمجية. المخاطر تشغيلية وليست متعلقة بالسرعة: volume باسم غير ثابت، أو bind mount بمالك ذو معرف مستخدم (user id) خاطئ، أو عملية استعادة لم تختبرها من قبل، وdocker compose down -v. عالج هذه النقاط الأربع وستعمل الحاوية بشكل جيد. انتقل إلى التثبيت المباشر على المضيف عندما تصبح قاعدة البيانات هي عبء العمل الرئيسي وتحتاج إلى pg_upgrade، أو النسخ المتماثل (replication)، أو الاستعادة إلى نقطة زمنية محددة (point in time recovery).

هل يجب أن أستخدم bind mount أم named volume لبيانات قاعدة البيانات؟

استخدم named volume ما لم يكن لديك سبب محدد لمعرفة مسار المضيف. ينشئ Docker الدليل ويقوم entrypoint الخاص بالصورة بضبط الملكية عند البدء لأول مرة، لذا لا تظهر مشكلة الصلاحيات أبداً. ثبّت الـvolume باستخدام name: صريح أو حدده كـexternal: true، وإلا فإن إعادة تسمية دليل المشروع ستؤدي بصمت إلى إنشاء volume جديد فارغ وقاعدة بيانات فارغة. الـbind mount خيار جيد إذا قمت بضبط ملكية دليل المضيف (chown) إلى معرف المستخدم الرقمي الذي تعمل به الصورة، وهو 999 للصور الرسمية لـPostgres وMySQL وMongoDB. تحقق من ذلك باستخدام ls -ldn، لأن ls -l يعرض اسم المستخدم على مضيفك لهذا الرقم، وهذا الاسم لا معنى له داخل الحاوية.

ماذا يحذف الأمر docker compose down -v؟

هو يزيل الحاويات والشبكة مثل الأمر down العادي، بينما يقوم -v بالإضافة إلى ذلك بإزالة كل named volume مُعرّف في ملف compose ذلك، إلى جانب كل anonymous volume متصل بتلك الحاويات. وهذا يشمل قاعدة البيانات. لا توجد رسالة تأكيد ولا إمكانية للاستعادة. الـvolumes المحددة كـexternal: true لا تُحذف، وهذا هو السبب الرئيسي لتحديد volume قاعدة البيانات كـexternal. لإعادة التشغيل الروتينية، استخدم docker compose stop وdocker compose start بدلاً من ذلك.

كيف أقوم بترقية PostgreSQL إلى إصدار رئيسي جديد في Docker؟

عن طريق Dump وRestore. تغيير postgres:16 إلى postgres:17 وإعادة التشغيل سيؤدي إلى FATAL: database files are incompatible with server مع سطر DETAIL يذكر كلا الإصدارين، لأن الملفات الثنائية الجديدة لن تقرأ هيكلية الكتالوج القديمة. لا شيء يتضرر: أعد الوسم (tag) القديم وستعمل. خذ نسخة pg_dumpall باستخدام عميل الإصدار الجديد مقابل الحاوية القديمة التي تعمل، وتأكد من أن الملف ينتهي بـPostgreSQL database cluster dump complete، ثم شغّل الوسم الجديد على volume فارغ وقم بتحميل النسخة. التحديثات الفرعية داخل الإصدار الرئيسي الواحد تتطلب فقط pull وإعادة تشغيل.

لماذا تخرج حاوية قاعدة البيانات الخاصة بي برمز الخطأ 137؟

137 هي 128 زائد الإشارة 9، مما يعني أن شيئاً ما أنهى العملية فوراً. شغّل docker inspect <container> | grep -i oomkilled؛ القيمة true تعني أن الحاوية وصلت إلى حد الذاكرة الخاص بـcgroup. السبب المعتاد هو أن PostgreSQL وMySQL يقرآن إجمالي الذاكرة من المضيف ولا يريان حد الحاوية، لذا يخططان لـ16 GB بينما يعملان داخل 2 GB. اضبط shared_buffers وwork_mem، أو innodb_buffer_pool_size، لتناسب الحد الذي منحته للحاوية. تحقق من journalctl -k بحثاً عن سطر Memory cgroup out of memory المطابق لتأكيد العملية التي اختارها النواة (kernel) للإنهاء.