ما الذي يتغير عند تشغيل Docker على VPS؟
اكتشف ما يحدث فعلياً عند تشغيل Docker على VPS: نفاد RAM، تجاوز المنافذ المنشورة لجدار UFW، توقف الحاويات بعد إعادة التشغيل، وامتلاء القرص.
ما الذي يتغير عند تشغيل Docker على VPS
يعمل Docker على VPS باستخدام المحرك نفسه والصور نفسها التي يستخدمها Docker على حاسوبك، لذلك تستمر جميع الأوامر التي تعرفها في العمل. لكن ما يتغير هو الموارد المحيطة به. يحتوي الحاسوب المحمول على ذاكرة احتياطية، وجدار ناري لا يبحث عنه أحد، وقرص كبير بما يكفي بحيث لا تضطر إلى مراقبته. أما الخادم المستأجر فلديه حد ثابت للذاكرة، وعنوان IP عاماً يبدأ فحصه خلال دقائق من الإقلاع، ونظام ملفات root سيملؤه Docker دون طلب إذن.
تسبب أربعة اختلافات معظم المشكلات على خادم صغير:
- الذاكرة محدودة، وتتعامل النواة مع نقصها بإنهاء إحدى العمليات.
- يمر المنفذ المنشور مباشرة عبر UFW (uncomplicated firewall)، لأن Docker يكتب قواعد الجدار الناري الخاصة به.
- لا تعود الحاويات إلى العمل بعد إعادة التشغيل ما لم تطلب ذلك مسبقاً.
- تستمر الصور والحاويات ووحدات التخزين وذاكرة التخزين المؤقت للبناء في النمو حتى يمتلئ القرص.
يسمّي كل قسم أدناه العطل، والنص الذي ستراه فعلياً، والدليل الذي يعالج المشكلة بالتفصيل. إذا لم تكتب ملف compose بعد، فاقرأ أساسيات Docker Compose على VPS أولاً ثم عُد إلى هنا. تفترض هذه الصفحة أنك تستطيع بالفعل تشغيل مجموعة الخدمات.
ما مقدار ذاكرة RAM التي تستخدمها حاوية Docker؟
أقل مما يتوقعه معظم الناس. الحاوية عملية ضمن cgroup (مجموعة تحكم)، وليست آلة افتراضية، لذلك لا توجد نواة ضيف ولا تخصيص ثابت. التكلفة هي مقدار الذاكرة التي تلمسها العملية داخلها. لذلك يمكن لمكدس كامل أن يعمل ضمن 2 GB، بينما لا يمكن للمكدس نفسه المبني من آلات افتراضية أن يعمل ضمنها.
الأرقام أدناه هي أرقام خمول نموذجية للصور القياسية على Ubuntu 24.04 بالإعدادات الافتراضية، وقد قُرئت من docker stats بعد بضع دقائق من التشغيل. استخدمها نقطة بداية للتخطيط، وليست معياراً لحمل العمل لديك. شغّل docker stats --no-stream على جهازك قبل الاعتماد على أي رقم، بما في ذلك هذه الأرقام.
The data behind this chart
[
{
"label": "nginx",
"idle_mb": 8,
"budget_mb": 64
},
{
"label": "Redis 7",
"idle_mb": 12,
"budget_mb": 128
},
{
"label": "Traefik v3",
"idle_mb": 40,
"budget_mb": 128
},
{
"label": "PostgreSQL 16",
"idle_mb": 45,
"budget_mb": 512
},
{
"label": "Uptime Kuma",
"idle_mb": 95,
"budget_mb": 256
},
{
"label": "MariaDB 11",
"idle_mb": 190,
"budget_mb": 512
},
{
"label": "Nextcloud (Apache)",
"idle_mb": 210,
"budget_mb": 768
}
]يؤدي العمودان وظيفتين مختلفتين. يوضح idle_mb ما تستخدمه الحاوية عندما لا تنفذ أي عمل. أما budget_mb فهو المقدار الذي ينبغي حجزه عند التخطيط، لأن الاستخدام الفعلي لا يكون في حالة خمول. تستهلك PostgreSQL نحو 45 MB أثناء الخمول، وتحتاج إلى 512 MB بعد بدء الاتصالات والفرز واستخدام ذاكرة التخزين المؤقت. استخدم عمود الميزانية في التخطيط. واستخدم عمود الخمول في تصحيح الأخطاء.
لاحظ شكل هذه الصفوف وعددها 7. تستهلك nginx نحو 8 MB أثناء الخمول، بينما تستهلك Nextcloud نحو 210 MB. يكاد الـproxy الموجود أمام تطبيقاتك لا يستهلك شيئاً. أما قاعدة البيانات وتطبيق PHP فهما ما يجب أن تحسب له حساباً عند تحديد مواصفات الخادم.
تنبيه بشأن docker stats: تتضمن قيمة الذاكرة page cache التي جلبتها قراءات ملفات الحاوية نفسها، ولذلك ترتفع لبعض الوقت بعد التشغيل ثم تستقر. راقبها لمدة ساعة قبل أن تقرر وجود تسرّب.
تحديد حجم VPS: ما الذي يتسع له 2 GB و4 GB و8 GB
احجز حصة المضيف أولاً. تعمل kernel وsystemd وjournald وsshd وDocker daemon في ذاكرة RAM نفسها التي تستخدمها حاوياتك، ويستهلك dockerd مع containerd نحو 100 MB منها. تحتاج أيضاً إلى ذاكرة حرة لـpage cache، وإلى الزيادة المؤقتة عند تشغيل image build أو database dump.
The data behind this chart
[
{
"plan": "2 GB VPS",
"total_mb": 2048,
"host_reserve_mb": 768,
"container_mb": 1280
},
{
"plan": "4 GB VPS",
"total_mb": 4096,
"host_reserve_mb": 1024,
"container_mb": 3072
},
{
"plan": "8 GB VPS",
"total_mb": 8192,
"host_reserve_mb": 1536,
"container_mb": 6656
}
]يغطي host_reserve_mb نظام التشغيل وDocker daemon والهامش الذي يحافظ على استجابة الخادم تحت الحمل. وما يتبقى هو container_mb، وهذا هو الرقم الوحيد المتاح لك للاستهلاك. يزداد الاحتياطي مع الخطة، من 768 MB في أصغر خادم إلى 1536 MB في أكبر خادم، لأن الخادم الأكبر يشغّل حاويات أكثر، ويكتب سجلات أكثر، ويحتاج إلى page cache أكبر.
تترك خطة بسعة 2 GB مقدار 1280 MB للحاويات. خصص 512 MB منها لـPostgreSQL و128 MB لـTraefik، وستكون قد استهلكت نصفها تقريباً. ويتبقى ما يكفي لتطبيقين صغيرين، بواقع 256 MB تقريباً لكل تطبيق. هذا خادم فعلي ومفيد. لكنه لا يتسع لـNextcloud وعنقود بحث إضافي.
تترك خطة بسعة 4 GB مقدار 3072 MB، وهو ما يكفي لتشغيل قاعدة بيانات وreverse proxy وثلاثة تطبيقات وحاوية monitoring في الوقت نفسه. وهذا أصغر حجم يستحق الاستخدام لأي خدمة مهمة، لأن الذاكرة المتبقية هي التي تستوعب آثار عملية نشر سيئة.
تترك خطة بسعة 8 GB مقدار 6656 MB من أصل 8192 MB، وعادةً ما ينتقل القيد من الذاكرة إلى CPU أو إلى معدل نقل البيانات على القرص. إذا أظهرت الحسابات أن مكدسك لا يتسع، فاشترِ الخطة الأكبر بدلاً من محاولة ضبطه ليتوافق معها: يوضح ما التكلفة الفعلية لـVPS قيمة الجيجابايت الإضافية شهرياً.
هناك قاعدتان تحافظان على دقة الحسابات. ضع حداً للذاكرة على كل خدمة، حتى لا تتمكن عملية خارجة عن السيطرة من استهلاك الخادم كله. واترك جزءاً من الميزانية غير مستخدم، لأن docker compose build وpg_dump يحتاجان إلى الذاكرة في أسوأ لحظة. يوضح حدود الذاكرة في Docker Compose الصياغة والمشكلات الشائعة.
لماذا تخرج الحاوية بالرمز 137؟
لأن kernel أنهى تشغيلها. الرمز 137 يساوي 128 مضافاً إليه 9، والإشارة 9 هي SIGKILL. طلبت الحاوية ذاكرة أكبر مما سُمح لها به، فأنهى OOM killer تشغيلها.
CONTAINER ID IMAGE STATUS
9f2c1a4d7b3e postgres:16 Exited (137) 4 minutes agoأكد السبب بدلاً من التخمين:
docker inspect my-db | grep -i oomkilled
sudo journalctl -k | grep -i "out of memory"يعني "OOMKilled": true أن الحاوية بلغت حد cgroup الخاص بها، ويذكر سجل kernel العملية التي اختارها:
Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kBهذه هي الحالة الجيدة، لأن الضرر بقي محصوراً في حاوية واحدة. أما الحالة السيئة فهي وجود حاوية بلا أي حد. عندئذ يكون سقفها هو كامل الجهاز، لذلك يستنزف تسرب الذاكرة في خدمة واحدة موارد المضيف، ثم يختار kernel عملية لإنهائها وفق حجمها على مستوى النظام بأكمله. يفقد سطر السجل البادئة Memory cgroup ويصبح Out of memory: Killed process 2417 (postgres). وغالباً ما يختار kernel قاعدة بياناتك، بينما تواصل الحاوية التي تسببت في التسرب تشغيلها. لذلك فإن وضع حد لكل خدمة أهم من القيمة الدقيقة لأي حد منفرد.
يغيّر Swap توقيت المشكلة، ولا يغيّر الحساب. تأتي معظم صور VPS من دون Swap. تحقق باستخدام swapon --show، إذ لا يطبع شيئاً عندما لا يكون Swap موجوداً. يوفّر ملف Swap لـkernel مكاناً لوضع الصفحات غير النشطة، ما يمنحك بضع دقائق لملاحظة المشكلة.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hيجب أن يعرض free -h الآن قيمة إجمالية غير صفرية في صف Swap. لا يضيف Swap ذاكرة RAM. يصبح الجهاز الذي يعاني ضغطاً مستمراً على الذاكرة بطيئاً إلى درجة تمنعك من الاتصال به عبر SSH لإصلاحه، لذلك تعامل مع Swap كاحتياطي مؤقت للتنبيه، وأصلح تقدير الموارد.
لماذا لا يحظر UFW منفذ Docker المنشور؟
لأن حركة الشبكة لا تصل إلى السلسلة التي يحميها UFW. عند نشر منفذ باستخدام -p 5432:5432 أو باستخدام إدخال ports: في Compose، يضيف البرنامج الخدمي قاعدة DNAT (ترجمة عنوان شبكة الوجهة) إلى جدول nat، ويضيف قاعدة قبول إلى سلسلته DOCKER الخاصة. تُمرَّر الحزمة المتجهة إلى حاوية إلى تلك الحاوية بدلاً من تسليمها إلى المضيف، لذلك تُعالَج ضمن مسار FORWARD ولا تمر أبداً عبر قواعد INPUT التي ينشئها UFW.
يمكنك مراقبة ذلك على الخادم:
sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432قد يعرض UFW 5432 DENY IN Anywhere، بينما يحتوي جدول nat على قاعدة DNAT tcp ... to:172.18.0.2:5432 للمنفذ نفسه. ومن جهاز آخر، يظل nc -vz your.server.ip 5432 متصلاً. تكون قاعدة البيانات متاحة على الإنترنت العام، بينما يعرض الجدار الناري أنها غير متاحة.
الحل هو تقليل المنافذ المنشورة. تشترك الحاويات في مشروع Compose واحد في شبكة، ويمكنها الوصول إلى بعضها باستخدام اسم الخدمة. لذلك لا تحتاج قاعدة البيانات التي تخدم التطبيق المجاور لها فقط إلى إدخال ports:. وعندما تحتاج إلى وصول محلي، اربط النشر بواجهة loopback:
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"بعد docker compose up -d، يفشل nc -vz your.server.ip 5432 من خارج الخادم، بينما يظل psql -h 127.0.0.1 -p 5432 على الخادم نفسه يعمل. في حزمة صغيرة سليمة، لا ينشر المنافذ إلا الـreverse proxy، على المنفذين 80 و443. يشرح لماذا تتجاوز المنافذ المنشورة في Docker UFW سلسلة DOCKER-USER للحالات التي يجب فيها نشر منفذ مع الاستمرار في تصفيته، بينما يشرح أساسيات جدار UFW الناري قواعد المضيف الأساسية.
لماذا اختفت حاوياتي بعد إعادة التشغيل؟
لأن شيئاً لم يطلب منها التشغيل مجدداً. تُنشأ الحاوية باستخدام سياسة إعادة التشغيل no ما لم تحدد سياسة أخرى، لذلك تتركها إعادة التشغيل متوقفة، ولا يهتم daemon بذلك. عمليات إعادة التشغيل ليست نادرة على VPS: فتحديثات kernel عبر unattended upgrades، وصيانة المزوّد، وتسلسل OOM المذكور أعلاه تنتهي جميعها بإعادة تشغيل واحدة.
يجب تحقق أمرين. يجب أن يبدأ daemon عند الإقلاع:
systemctl is-enabled dockerيعرض ذلك enabled في تثبيت Ubuntu القياسي. بعد ذلك، تحتاج كل خدمة إلى سياسة:
services:
app:
image: ghcr.io/example/app:1.4
restart: unless-stoppedتعيد unless-stopped تشغيل الحاوية بعد إعادة التشغيل، وتحترم الحاوية التي أوقفتها عن قصد. أما always فتعيد أيضاً تشغيل الحاويات التي أوقفتها عمداً كلما أعاد daemon التشغيل، وقد يفاجئك ذلك أثناء تصحيح الأخطاء. لا يكفي تعديل الملف وحده، لأن سياسة إعادة التشغيل تُحدَّد عند إنشاء الحاوية. شغّل docker compose up -d لإعادة إنشائها، ثم تحقق من القيمة الفعلية:
docker inspect my-app | grep -A3 RestartPolicyبعد ذلك، أعد تشغيل الخادم عن قصد وشغّل docker compose ps من دليل المشروع. إذا صمدت الحزمة أمام إعادة تشغيل مخططة، فستصمد أمام إعادة تشغيل غير مخططة. إذا كانت الحزمة تحتاج إلى ضمان لترتيب التشغيل أو إلى مهمة تُنفَّذ مرة واحدة عند الإقلاع، فإن وحدة systemd هي الأداة الأفضل: يتضمن تشغيل Docker Compose عند الإقلاع ملف الوحدة. ولمعرفة ما إذا كانت الحاوية التي عادت إلى التشغيل تقدم الخدمة فعلاً، أضف فحوصات صحة Compose.
لماذا امتلأ قرص VPS لدي؟
لأن Docker يحتفظ بكل شيء إلى أن تطلب منه الحذف. تبقى كل وسم صورة سحبته، وكل حاوية متوقفة، وكل volume مجهول خلّفته عملية إعادة الإنشاء، وكل طبقة من ذاكرة التخزين المؤقت للبناء على القرص. في نظام ملفات root بسعة 40 GB أو 80 GB، وهو أمر طبيعي مع أحجام هذه الخطط، يتحول ذلك إلى انقطاع في الخدمة خلال أشهر بدلاً من سنوات.
لا يبدو امتلاء القرص كتعطل مفاجئ. ستتلقى no space left on device من حاوية، وapt، ومن journald، ومن docker pull خلال الساعة نفسها. سيتوقف PostgreSQL عن قبول عمليات الكتابة. وسيبقى الخادم قيد التشغيل، ما يجعل اكتشاف المشكلة أصعب من اكتشاف حلقة إعادة التشغيل.
تحقق قبل الحذف:
docker system df
df -h /يقسم docker system df الإجمالي إلى الصور والحاويات ووسائط التخزين المحلية وذاكرة التخزين المؤقت للبناء، ويعرض بجانب كل منها عمود RECLAIMABLE. في الخادم الذي ينشئ صوره بنفسه، تكون ذاكرة التخزين المؤقت للبناء عادةً أكبر بند.
docker image prune -a
docker builder prune
docker system dfيحذف docker image prune -a كل صورة لا تستخدمها أي حاوية. ويمسح docker builder prune ذاكرة التخزين المؤقت للبناء. كلا الأمرين آمن أثناء تشغيل الخدمات، لأن العناصر المستخدمة لا تُحذف. لكن docker system prune --volumes غير آمن، لأنه يحذف كل volume لا تشير إليه أي حاوية حالياً. إن المكدس الذي أوقفته لعطلة نهاية الأسبوع يكون بهذه الحالة تماماً، وسيُحذف volume قاعدة بياناته معه. اقرأ الفرق بين bind mounts وnamed volumes قبل كتابة ذلك الخيار، وأنشئ نسخة احتياطية أولاً.
تُعد سجلات الحاويات سبباً أبطأ لنمو الاستخدام. لا يفرض برنامج التشغيل الافتراضي json-file أي حد للحجم، لذلك قد تكتب حاوية كثيرة الرسائل عدة GB في /var/lib/docker/containers. حدّد الحجم لكل حاوية في /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}طبّق الإعداد باستخدام sudo systemctl restart docker، الذي يعيد تشغيل حاوياتك، لذلك اختر الوقت المناسب. ينطبق الحد على الحاويات التي تُنشأ بعد التغيير، لذا أعد إنشاء الحاويات قيد التشغيل باستخدام docker compose up -d --force-recreate، ثم تحقّق:
docker inspect my-app | grep -A5 LogConfig
sudo du -sh /var/lib/docker/containersيجب أن يُظهر ناتج inspect ضبط max-size. إذا كان فارغاً، فهذا يعني أن الحاوية أُنشئت قبل التغيير وما زالت تكتب بلا حد.
عادات تحافظ على سلامة خادم Docker صغير
لا تحتاج أي من هذه الإجراءات إلى لوحة معلومات أو أداة عليك تعلّمها.
- شغّل
docker system dfوdf -h /في اليوم الأول من كل شهر. يستغرق الأمر ثلاثين ثانية باستخدام أمرين، ويتيح لك رؤية الاتجاه قبل أن يتحول إلى انقطاع. - حدّد حداً للذاكرة لكل خدمة، حتى الخدمات التي تظن أنها صغيرة بالتأكيد. يحوّل الحد انقطاعاً على مستوى الخادم إلى حاوية واحدة تجري إعادة تشغيلها.
- راقب الخادم من مكان آخر، لتعرف بوجود ضغط على الذاكرة أو القرص قبل أن يتدخل kernel. تعمل Uptime Kuma داخل حاوية، وتستخدم عند الخمول نحو 95 MB.
- انسخ volumes احتياطياً، لا الحاويات. الحاوية قابلة للاستبدال، أما volume فليس كذلك. يغطي النسخ الاحتياطي باستخدام restic على VPS جدولة النسخ واختبار الاستعادة.
- ثبّت وسوم image في ملف compose، وحدّثها في يوم تختاره. عند استخدام
latest، تكون النسخة التي تحصل عليها منdocker compose pullالتالي هي النسخة التي صدرت في ذلك الصباح.
يبقى VPS صغير يشغّل Docker سليماً لسنوات عندما تظل أربعة أرقام ضمن النطاق: ميزانية الذاكرة، وقائمة المنافذ المنشورة، وسياسة إعادة التشغيل لكل خدمة، والمساحة الحرة على القرص. أما كل ما عدا ذلك فهو Docker نفسه الذي تشغّله في المنزل.
FAQ
ما مقدار RAM الذي أحتاج إليه لتشغيل Docker على VPS؟
استهلاك Docker نفسه منخفض. يستهلك كل من daemon وcontainerd معاً نحو 100 MB، بينما يعتمد باقي المتطلبات على الحاويات التي تشغّلها. احجز حصة المضيف أولاً: 768 MB على خادم سعته 2048 MB لنظام التشغيل وdaemon وهامش الأمان، وبذلك يتبقى 1280 MB للحاويات. يمكن أن تتسع هذه المساحة لقاعدة بيانات تستهلك 512 MB، وreverse proxy يستهلك 128 MB، وتطبيقين صغيرين. قِس مكدسك بنفسك باستخدام docker stats --no-stream بدلاً من الاعتماد على أي رقم منشور.
هل يمكنني تشغيل Docker على VPS بسعة 1 GB؟
نعم، لتشغيل حاوية خفيفة أو حاويتين. أضف ملف swap قبل البدء. يستهلك نظام التشغيل وDocker daemon نحو نصف خادم سعته 1 GB بعد تشغيلهما، لذلك يتبقى مجال لتطبيق صغير وreverse proxy، لكن ليس لقاعدة بيانات تحت حمل فعلي. سيفشل بناء الصور على خادم بهذه السعة أو قد يؤدي إلى إيقاف مكوّن آخر، لذلك ابنِ الصور على خادم آخر واسحب الصورة الجاهزة.
هل يحمي UFW حاوية Docker؟
ليس للمنافذ التي تنشرها. يكتب Docker قواعد DNAT وقواعد forward الخاصة به، لذلك تُوجَّه الحزمة المرسلة إلى منفذ حاوية منشور إلى الحاوية بدلاً من تسليمها إلى المضيف، ولا تصل إليها قواعد INPUT التي يديرها UFW. يمكن أن يكون ufw deny 5432 مفعّلاً بينما يستجيب ذلك المنفذ من الإنترنت. انشر المنفذ على loopback باستخدام 127.0.0.1:5432:5432، واترك الخدمات الداخلية غير منشورة، أو طبّق التصفية في سلسلة DOCKER-USER.
هل ستُعاد حاوياتي إلى التشغيل بعد إعادة تشغيل VPS؟
فقط إذا أُنشئت باستخدام restart policy. اضبط restart: unless-stopped لكل خدمة، وشغّل docker compose up -d حتى تُعاد إنشاء الحاويات بهذه السياسة، ثم تأكد من أن systemctl is-enabled docker يطبع enabled. بعد ذلك أعد التشغيل عمداً وتحقق باستخدام docker compose ps. سياسة إعادة تشغيل لم تختبرها من قبل ليست سياسة إعادة تشغيل موثوقة.
كم مرة ينبغي أن أحذف صور Docker غير المستخدمة؟
يكفي ذلك شهرياً لمعظم الخوادم الصغيرة، أو كلما أبلغ docker system df عن مساحة قابلة للاسترداد ستحتاج إليها. يُعد كل من docker image prune -a وdocker builder prune آمناً أثناء تشغيل الخدمات، لأن الصور وذاكرة التخزين المؤقت المستخدمة تُستثنى. تجنب docker system prune --volumes ما لم تعرف بدقة أي volumes غير مرتبطة، لأنه يحذف بيانات أي مكدس متوقف بالصدفة.