ما الذي يتغير فعليًا عند تشغيل 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 على خادمك قبل الاعتماد على أي رقم، بما في ذلك هذه الأرقام.
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
ابدأ بطرح حصة المضيف أولاً. تعمل النواة وsystemd وjournald وsshd وDocker daemon في ذاكرة RAM نفسها التي تستخدمها حاوياتك، ويستهلك dockerd مع containerd نحو 100 MB منها. تحتاج أيضاً إلى ذاكرة حرة لذاكرة التخزين المؤقت للصفحات، وللارتفاع المؤقت في الاستهلاك عند إنشاء image أو تشغيل تفريغ قاعدة بيانات.
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 في أكبر خادم، لأن الخادم الأكبر يشغّل حاويات أكثر، ويكتب سجلات أكثر، ويحتاج إلى ذاكرة تخزين مؤقت للصفحات أكبر.
تترك خطة 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 أو إلى معدل نقل القرص. هناك فئة من الحاويات تحدد حجمها من إعداداتها لا من الحمل: إذ يحجز خادم النموذج المحلي ذاكرة KV cache بما يتناسب مع نافذة السياق، لذلك يمكن أن يؤدي رفع قيمة num_ctx في Ollama إلى إضافة عدة GB إلى الميزانية قبل وصول طلب واحد. إذا أظهرت الحسابات أن مجموعتك لا يتسع لها الخادم، فاشترِ الخطة الأكبر بدلاً من محاولة ضبط الإعدادات لتجاوز المشكلة: يوضح التكلفة الفعلية لـVPS قيمة GB الإضافية شهرياً.
تساعد قاعدتان على إبقاء الحسابات دقيقة. ضع حداً للذاكرة على كل خدمة، حتى لا تتمكن عملية خارجة عن السيطرة من إسقاط الخادم بأكمله. واترك الجزء العلوي من الميزانية غير مستخدم، لأن 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 مجهول خلّفته عملية إعادة الإنشاء، وكل طبقة من ذاكرة التخزين المؤقت للبناء على القرص. وعندما يكون نظام الملفات الجذري بحجم 40 GB أو 80 GB، وهو أمر طبيعي في هذه الخطط، يتحول ذلك إلى انقطاع خدمة خلال أشهر لا سنوات.
لا يبدو امتلاء القرص كتعطل. تحصل على 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 قاعدة بياناتها معها. اقرأ مقارنة بين 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 قبل البدء. يذهب نحو نصف خادم سعته 1 GB بعد تشغيل نظام التشغيل وDocker daemon، ما يترك مساحة لتطبيق صغير وreverse proxy، لكنه لا يكفي لقاعدة بيانات تحت حمل فعلي. سيفشل بناء الصور على خادم بهذه السعة أو سيؤدي إلى إيقاف مكوّن آخر، لذلك ابنِ الصور في مكان آخر ثم اسحب الصورة الجاهزة.
هل يحمي UFW حاوية Docker؟
ليس بالنسبة إلى المنافذ التي تنشرها. يكتب Docker قواعد DNAT وإعادة التوجيه الخاصة به، لذلك تُوجَّه الحزمة المتجهة إلى منفذ حاوية منشور إلى الحاوية بدلاً من تسليمها إلى الخادم، ولا ترى قواعد 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. إن لم تختبر restart policy، فلا يمكنك اعتبارها سياسة إعادة تشغيل موثوقة.
كم مرة ينبغي أن أحذف صور Docker غير المستخدمة؟
يكفي ذلك شهرياً لمعظم الخوادم الصغيرة، أو عندما يبلّغ docker system df عن مساحة قابلة للاسترداد ستحتاج إليها. يُعد كل من docker image prune -a وdocker builder prune آمناً أثناء تشغيل الخدمات، لأن الصور وذاكرة التخزين المؤقت المستخدمة تُستثنى. تجنب docker system prune --volumes ما لم تعرف بدقة أي volumes غير مرتبطة، لأنه يحذف بيانات أي مكدس متوقف مصادفة.