ما الذي يتغير فعليًا عند تشغيل Docker على VPS؟
اكتشف ما يتغير فعليًا عند تشغيل Docker على VPS: نفاد RAM، تجاوز المنافذ المنشورة لـ UFW، توقف الحاويات بعد إعادة التشغيل، وامتلاء القرص.
ما الذي يتغير عند تشغيل Docker على VPS
يعمل Docker على VPS باستخدام المحرك نفسه والصور نفسها التي يستخدمها Docker على حاسوبك المحمول، لذلك تظل كل الأوامر التي تعرفها تعمل. ما يتغير هو الموارد المتاحة حوله. يحتوي الحاسوب المحمول على ذاكرة احتياطية، وجدار ناري لا يفحصه أحد، وقرص كبير بما يكفي بحيث لا تحتاج إلى مراقبته. أما الخادم المستأجر، فله حد ثابت للذاكرة، وعنوان IP عام يبدأ فحصه خلال دقائق من الإقلاع، ونظام ملفات root سيملؤه Docker دون طلب تأكيد منك.
تسبب 4 اختلافات معظم المشكلات على الخادم الصغير:
- الذاكرة محدودة، وتعالج النواة نقصها بإنهاء إحدى العمليات.
- يمر المنفذ المنشور مباشرة عبر UFW (uncomplicated firewall)، لأن Docker يكتب قواعد الجدار الناري الخاصة به.
- لا تعود الحاويات إلى العمل بعد إعادة التشغيل إلا إذا طلبت ذلك مسبقاً.
- تزداد الصور والحاويات وvolumes وذاكرة التخزين المؤقت للبناء حتى يمتلئ القرص.
يسمي كل قسم أدناه سبب الفشل، والنص الذي ستراه فعلياً، والدليل الذي يعالج المشكلة بالتفصيل. إذا لم تكتب ملف compose بعد، فاقرأ أساسيات Docker Compose على VPS أولاً ثم عد إلى هنا. تفترض هذه الصفحة أنك تستطيع تشغيل stack بالفعل.
كم من RAM يستخدمه حاوي Docker؟
أقل مما يتوقع معظم الأشخاص. الحاوي هو عملية ضمن cgroup (مجموعة تحكم)، وليس آلة افتراضية، لذلك لا توجد نواة ضيف ولا تخصيص ثابت. تعتمد التكلفة على ما تلمسه العملية داخل الحاوي. لهذا يمكن لمكدس كامل أن يعمل ضمن 2 GB، بينما لا يمكن للمكدس نفسه المبني من آلات افتراضية أن يعمل ضمن السعة ذاتها.
الأرقام أدناه هي أرقام الخمول المعتادة للصور القياسية على Ubuntu 24.04 بالإعدادات الافتراضية، وقد قُرئت من docker stats بعد بضع دقائق من التشغيل. استخدمها كنقطة بداية للتخطيط، لا باعتبارها معياراً لحِمل العمل لديك. شغّل docker stats --no-stream على خادمك قبل الاعتماد على أي رقم، بما في ذلك هذه الأرقام.
البيانات وراء هذا الرسم البياني
[
{
"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
احسب حصة المضيف أولاً. تعمل النواة وsystemd وjournald وsshd وDocker daemon في ذاكرة RAM نفسها التي تستخدمها حاوياتك، وdockerd مع containerd يستهلكان نحو 100 MB منها. تحتاج أيضاً إلى ذاكرة حرة لذاكرة الصفحات المؤقتة، وإلى مساحة إضافية للارتفاع المفاجئ عند إنشاء image أو تشغيل تفريغ لقاعدة بيانات.
البيانات وراء هذا الرسم البياني
[
{
"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 في أكبر خادم، لأن الخادم الأكبر يشغّل حاويات أكثر، ويكتب سجلات أكثر، ويحتاج إلى ذاكرة صفحات مؤقتة أكبر.
تترك خطة 2 GB مقدار 1280 MB للحاويات. خصص 512 MB منها لـPostgreSQL و128 MB لـTraefik، وستكون قد استهلكت نصفها. يتبقى تطبيقان صغيران، بواقع نحو 256 MB لكل منهما. هذا خادم فعلي ومفيد. لكنه لا يتسع لـNextcloud وعنقود بحث إضافي.
تترك خطة 4 GB مقدار 3072 MB، وهو ما يكفي لتشغيل قاعدة بيانات وReverse Proxy وثلاثة تطبيقات وحاوية مراقبة في الوقت نفسه. هذا أصغر حجم يستحق الاستخدام لأي خدمة مهمة، لأن الذاكرة الاحتياطية هي التي تستوعب عملية نشر سيئة.
تترك خطة 8 GB مقدار 6656 MB من أصل 8192 MB، وعادةً ينتقل الحد هنا من الذاكرة إلى CPU أو إلى معدل نقل البيانات على القرص. لا تبيع SSD Nodes خطة أصغر؛ فأصغر خطة لديها تحتوي على 8 GB من RAM (تم التحقق من صفحة الأسعار في 2026-10-11). لذلك تنطبق هذه الفئة على ذلك المضيف، بينما تصف فئتا 2 GB و4 GB خوادم أصغر لدى مزودين آخرين. توجد فئة من الحاويات تحدد حجمها من إعداداتها، لا من الحمل: إذ يحجز خادم نموذج محلي ذاكرة KV cache بما يتناسب مع نافذة السياق، لذلك يمكن أن يؤدي رفع قيمة num_ctx في Ollama إلى إضافة غيغابايت إلى الميزانية قبل وصول طلب واحد. إذا أظهرت الحسابات أن مجموعتك لا يتسع لها الخادم، فاشترِ الخطة الأكبر بدلاً من محاولة ضبط الإعدادات لتجاوز المشكلة: يوضح ما التكلفة الفعلية لـVPS قيمة الغيغابايت الإضافية شهرياً.
تحافظ قاعدتان على دقة الحسابات. ضع حداً للذاكرة على كل خدمة، حتى لا تتمكن عملية خارجة عن السيطرة من استهلاك الخادم بأكمله. واترك الجزء العلوي من الميزانية غير مستخدم، لأن docker compose build وpg_dump يحتاجان كلاهما إلى الذاكرة في أسوأ لحظة. يتضمن حدود الذاكرة في Docker Compose الصياغة والمشكلات الشائعة.
لماذا يخرج الحاوي بالرمز 137؟
لأن النواة أنهته. الرمز 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 الخاص به، ويسمي سجل النواة العملية التي اختارها:
Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kBهذه هي الحالة الجيدة، لأن الضرر توقف عند حاوي واحد. أما الحالة السيئة فهي أن يعمل حاوي من دون أي حد. عندها يكون سقفه هو كامل الجهاز، لذلك يؤدي تسرب الذاكرة في خدمة واحدة إلى استنزاف موارد المضيف، ثم تختار النواة ضحية وفق حجم العملية على مستوى النظام بأكمله. يفقد سطر السجل البادئة Memory cgroup ويصبح Out of memory: Killed process 2417 (postgres). وغالباً ما تكون العملية التي تختارها هي قاعدة البيانات، بينما يستمر الحاوي الذي تسبب في التسرب بالعمل. لذلك، يهم وضع حد لكل خدمة أكثر من أهمية القيمة الدقيقة لأي حد منفرد.
يغيّر Swap توقيت المشكلة، لا الحساب. تأتي معظم صور VPS من دون Swap. تحقق باستخدام swapon --show، الذي لا يطبع شيئاً على الإطلاق عند عدم وجود Swap. يوفّر ملف Swap للنواة مكاناً تضع فيه الصفحات غير النشطة، وهذا يمنحك دقائق لملاحظة المشكلة.
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، يضيف daemon قاعدة DNAT (ترجمة عنوان شبكة الوجهة) إلى جدول nat، وقاعدة accept إلى سلسلته الخاصة 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 عبر التحديثات التلقائية، وصيانة المزوّد، وتسلسل 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 مجهول خلّفته عملية إعادة الإنشاء، وكل طبقة من ذاكرة التخزين المؤقت للبناء. يمنحك القرص الأكبر وقتاً إضافياً فقط. حتى قرص SSD بسعة 160 GB في أصغر خطة لدى SSD Nodes (تم التحقق من صفحة الأسعار في 2026-10-11) سيمتلئ بالطريقة نفسها على خادم يسحب الصور ويبنيها كل أسبوع، بينما يحوّل نظام الملفات الجذري الأصغر ذلك إلى انقطاع خدمة في وقت أقرب.
لا يبدو امتلاء القرص كأنه تعطل. ستتلقى no space left on device من حاوية، ومن apt، ومن journald ومن docker pull خلال الساعة نفسها. ويتوقف PostgreSQL عن قبول عمليات الكتابة. يظل الخادم قيد التشغيل، ولذلك يصعب اكتشاف المشكلة مقارنة بحالة تكرار إعادة التشغيل.
تحقق قبل الحذف:
docker system df
df -h /يقسم docker system df الإجمالي إلى الصور والحاويات وvolumes المحلية وذاكرة التخزين المؤقت للبناء، مع عمود RECLAIMABLE بجانب كل عنصر. في الخادم الذي يبني صوره بنفسه، تكون ذاكرة التخزين المؤقت للبناء عادةً أكبر بند.
docker image prune -a
docker builder prune
docker system dfيحذف docker image prune -a كل صورة لا تستخدمها أي حاوية. يمسح docker builder prune ذاكرة التخزين المؤقت للبناء. كلا الأمرين آمن أثناء تشغيل الخدمات، لأنهما يتجاوزان كل ما هو مستخدم. لكن docker system prune --volumes غير آمن. فهو يحذف الحاويات المتوقفة، ثم كل volume مجهول لا تستخدمه أي حاوية متبقية. تكون البنية التي أوقفتها لعطلة نهاية الأسبوع بهذا الشكل تماماً. وإذا كانت قاعدة بياناتها تحفظ بياناتها في volume مجهول، وهو ما ينشئه Docker عندما تعلن الصورة عن volume ولا يربط ملف compose الخاص بك شيئاً به، فستُحذف تلك البيانات معه. تبقى volumes المسماة بعد تنفيذ ذلك الأمر، لكن docker volume prune --all يحذف volumes المسماة غير المستخدمة أيضاً. اقرأ bind mounts مقابل volumes المسماة قبل كتابة أي من الخيارين، وأنشئ نسخة احتياطية أولاً.
سجلات الحاويات هي مصدر النمو الأقل وضوحاً. لا يفرض برنامج التشغيل الافتراضي json-file أي حد للحجم، لذلك قد تكتب حاوية كثيرة الرسائل غيغابايتات في /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 /في اليوم الأول من كل شهر. يستغرق الأمران 30 ثانية، ويتيحان لك رؤية الاتجاه قبل أن يتحول إلى انقطاع في الخدمة بوقت طويل. - حدّد حداً للذاكرة لكل خدمة، بما في ذلك الخدمات التي تظن أنها صغيرة بالتأكيد. يحوّل الحدّ انقطاعاً على مستوى الخادم إلى حاوية واحدة يعاد تشغيلها.
- راقب الخادم من مكان آخر، لتعرف بوجود ضغط على الذاكرة أو القرص قبل أن يتدخل 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 غير مرتبطة، لأنه يحذف volumes المجهولة الخاصة بأي مكدس متوقف، كما أن docker volume prune --all يشمل أيضاً volumes المسمّاة غير المستخدمة.