SSD Nodes Learn 8GB RAM — $66/سنة
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-01

تشغيل Docker Compose تلقائيًا بعد إعادة تشغيل الخادم

اجعل خدمات Docker Compose تعود بعد إعادة التشغيل عبر سياسات restart، وتعرّف إلى سبب عدم كفاية on-failure ومتى تحتاج إلى وحدة systemd لترتيب بدء الخدمات.

الإجابة المختصرة

تبدأ خدمات Docker Compose عند إقلاع النظام عندما يتحقق شرطان في الوقت نفسه. يجب تمكين Docker daemon كخدمة نظام، ويجب أن تتضمن كل خدمة في الملف سياسة إعادة تشغيل هي unless-stopped أو always. أضف restart: unless-stopped إلى كل خدمة، وشغّل docker compose up -d مرة واحدة، وستعود الحاويات إلى العمل تلقائيًا بعد إعادة التشغيل. لا تحتاج إلى أي إجراء آخر في الحالة الشائعة.

تحتاج إلى وحدة systemd فقط عندما يكون الترتيب مهمًا: مثلًا عندما تعتمد مجموعة خدمات على قرص موصول، أو واجهة VPN، أو مشاركة شبكة لا تكون جاهزة عند بدء Docker daemon. هذه الحالة واردة، ويغطيها النصف الثاني من هذا الدليل. إذا كنت لا تزال تتعلم أساسيات تعريفات الخدمات ووحدات التخزين، فابدأ بـ أساسيات Docker Compose على VPS ثم عد إلى هنا.

تعيين سياسة إعادة التشغيل في compose.yaml

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

services:
  app:
    image: nginx:1.27
    restart: unless-stopped
    ports:
      - "8080:80"
  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: change-me
    volumes:
      - dbdata:/var/lib/postgresql/data

volumes:
  dbdata:

طبّق التغيير، ثم اقرأ السياسة من الحاوية قيد التشغيل:

docker compose up -d
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app)

يطبع ذلك unless-stopped. إذا طبع no، فهذا يعني أنه جرى تعديل الملف، لكن لم تتم إعادة إنشاء الحاوية.

هذا هو سبب الفشل الأكثر شيوعًا. تُخزَّن سياسة إعادة التشغيل في الحاوية، وليس في ملف YAML. لا يغيّر تعديل compose.yaml أي شيء في حاوية موجودة مسبقًا. ولا يفيد docker compose restart أيضًا، لأنه يوقف كائن الحاوية نفسه ثم يعيد تشغيله من دون تعديل إعداداته. وحده docker compose up -d يقارن الملف بالحاويات قيد التشغيل، ويكتشف تغيّر السياسة، ثم يعيد إنشاء الحاويات.

بالنسبة إلى حاوية لا تريد إعادة إنشائها الآن، غيّر السياسة مباشرة:

docker update --restart unless-stopped my-container

عدّل ملف YAML أيضًا. يغيّر docker update الحاوية قيد التشغيل، بينما سيقرأ docker compose up -d التالي الملف ويعيد القيمة القديمة.

ما الذي تفعله كل قيمة لإعادة التشغيل فعليًا

يعرّف Docker أربع قيم. ولا يظهر الفرق بينها إلا عند إعادة تشغيل الجهاز أو إعادة تشغيل البرنامج الخفي.

  • no هي القيمة الافتراضية. لا يُعاد تشغيل الحاوية تلقائيًا تحت أي ظرف.
  • always تعيد تشغيل الحاوية كلما توقفت. إذا أوقفتها يدويًا، فستعمل مجددًا عند بدء تشغيل البرنامج الخفي لـ Docker في المرة التالية. وقد يكون ذلك مفاجئًا غالبًا: فالحاوية التي أوقفتها عمدًا في الأسبوع الماضي تعمل مجددًا بعد إعادة التشغيل.
  • unless-stopped تعمل مثل always، باستثناء أن الحاوية التي أوقفتها يدويًا تبقى متوقفة بعد إعادة تشغيل البرنامج الخفي. هذه هي القيمة المناسبة لخدمة توقفها أحيانًا لإجراء الصيانة.
  • on-failure تعيد تشغيل الحاوية فقط عند خروجها برمز خروج غير صفري. ويمكنك تحديد حد لعدد المحاولات، كما في restart: on-failure:3.

بالنسبة إلى مكدس يجب أن يكون قيد التشغيل كلما كان الخادم قيد التشغيل، فإن unless-stopped هي القيمة الافتراضية المناسبة. اختر always فقط عندما تريد حاوية يصعب أن تبقى متوقفة.

لماذا لا يصمد on-failure أمام إعادة التشغيل

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

إعادة التشغيل ليست خطأ. عند إيقاف تشغيل المضيف، يوقف systemd ‏docker.service، ثم يوقف البرنامج الخدمي كل حاوية عمدًا. لم تتعطل الحاوية، لذلك لا توجد حالة تستجيب لها السياسة. عند الإقلاع مجددًا، يفحص البرنامج الخدمي الحاويات التي يجب استئنافها، ولا تكون حاوية on-failure التي أُوقفت بشكل سليم من بينها. وتبقى في حالة exited.

يمكنك رؤية ذلك مباشرة. اضبط restart: on-failure على خدمة، ونفّذ docker compose up -d، ثم أعد التشغيل، ونفّذ:

docker compose ps -a

تظهر الخدمة بحالة Exited وحالة مثل Exited (0) 2 minutes ago. لا يوجد عطل ولا يُسجَّل أي خطأ، وهذا ما يصعّب تشخيص المشكلة. نفّذت السياسة ما تنص عليه بالضبط.

لا يزال on-failure مفيدًا. فهو مناسب لحاوية تشغّل مهمة وقد تتعطل، عندما تريد عددًا محدودًا من المحاولات من دون حلقة إعادة تشغيل. لكنه ليس الأداة المناسبة لإبقاء خدمة طويلة التشغيل على قيد العمل بعد إعادة التشغيل.

لا تعمل سياسات إعادة التشغيل إلا إذا بدأت خدمة Docker عند الإقلاع

يفرض عفريت Docker سياسات إعادة التشغيل. إذا لم يبدأ العفريت، فلن يفرض أي شيء. تحقّق من ذلك:

systemctl is-enabled docker
systemctl is-enabled containerd

يجب أن يطبع كلا الأمرين enabled. تعمل الحزم من مستودع Docker الرسمي على تمكينهما وقت التثبيت، لذلك ينجح هذا عادةً على خادم جديد. إذا طبع أيٌّ منهما disabled، فأصلح ذلك:

sudo systemctl enable --now docker containerd

هناك نقطة مهمة ينبغي فهمها هنا. يتضمن Ubuntu أيضًا docker.socket، الذي يبدأ العفريت عند الطلب في المرة الأولى التي يتصل فيها شيء ما بواجهة Docker API. يرى المستخدمون أن docker.socket مُمكّن، ويفترضون أن العفريت مشمول، ثم يعطّلون docker.service لتوفير الذاكرة. عند الإقلاع، لا يستدعي شيء واجهة API، لذلك لا يُلمَس المقبس، ولا يبدأ العفريت، ولا تبدأ أي حاوية حتى تكتب أول أمر docker. لا يُعد تنشيط المقبس بديلًا عن تمكين docker.service.

متى تكون وحدة systemd هي الحل الأفضل

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

اكتب وحدة systemd عند انطباق أي من الحالات التالية. تحتاج المكدسة إلى نقطة ربط، أو واجهة VPN، أو وحدة أخرى يجب أن تصبح جاهزة أولًا. تريد أن يعمل systemctl stop myapp وsystemctl start myapp بالطريقة نفسها التي يعملان بها مع كل خدمة أخرى على الخادم. أو تريد إيقاف المكدسة بشكل سليم أثناء إيقاف التشغيل، بدلًا من إنهائها مع البرنامج الخدمي. إذا كانت وحدات systemd جديدة عليك، يشرح كتابة خدمة ومؤقت في systemd تنسيق الملف بمزيد من التفصيل.

كتابة وحدة systemd

ضع الحزمة في مسار ثابت خارج الدليل المنزلي. يُعد /srv/myapp خيارًا جيدًا، لأن الوحدة التي تعمل قبل أن يسجّل أي شخص الدخول لا تحتاج إلى قراءة /home.

أنشئ /etc/systemd/system/myapp.service:

[Unit]
Description=myapp docker compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
RequiresMountsFor=/srv/myapp/data

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

فعّلها وابدأ تشغيلها:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.service

تعرض الوحدة السليمة Active: active (exited). قد يبدو ذلك غير صحيح في المرة الأولى التي تراه فيها. لكنه صحيح: يعني Type=oneshot مع RemainAfterExit=yes أن الوحدة نفّذت أمرها، وانتهى الأمر، وأن systemd يبقي الوحدة معلّمة بأنها نشطة حتى يعمل ExecStop عند إيقاف التشغيل.

لكل سطر وظيفة. يعني Requires=docker.service أن الوحدة تفشل سريعًا بدلًا من تشغيل docker compose على socket متوقف. يحدد After= الترتيب، لأن Requires= وحده لا يحدده. يجعل RequiresMountsFor= systemd يضمّن وحدة mount لهذا المسار وينتظرها، وهذا هو سبب استخدام وحدة بدلًا من سياسة إعادة التشغيل. يمنع TimeoutStartSec=0 systemd من إنهاء مهمة البدء بينما لا تزال تُسحب صورة كبيرة.

ملاحظة حول الجمع بين الآليتين. توصي وثائق Docker بعدم خلط سياسات إعادة التشغيل مع مدير عمليات على المضيف. يتعلق هذا التحذير بمدير عمليات يشرف على عملية الحاوية نفسها ويعيد تشغيلها بينما يحاول daemon تنفيذ المهمة نفسها. لا تشرف وحدة Type=oneshot على أي عملية، لذلك لا مشكلة في إبقاء restart: unless-stopped في ملف compose إلى جانب هذه الوحدة، وهذا هو المطلوب. يتولى systemd ترتيب التشغيل عند الإقلاع، بينما يتولى daemon التعامل مع حاوية تتعطل في الثالثة صباحًا.

تحقّق باستخدام إعادة تشغيل فعلية

لا يوجد بديل عن الاختبار الفعلي. لا يختبر systemctl restart docker ترتيب تحميل أنظمة الملفات، ولا يختبر docker compose down المتبوع بـ docker compose up -d أي جانب من جوانب الإقلاع.

sudo reboot

انتظر، ثم أعد الاتصال، وتحقّق بالترتيب التالي:

uptime
systemctl is-active docker
docker compose ps

يؤكد uptime أنك تتعامل مع جهاز أُعيد تشغيله فعليًا. أما docker compose ps، عند تشغيله من مجلد الحزمة، فيُفترض أن يعرض كل خدمة بالحالة running، مع مدة تشغيل قريبة من مدة تشغيل الجهاز. الخدمة التي تظهر بالحالة Exited هي التي ينبغي فحصها.

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

journalctl -u docker.service -b --no-pager | tail -50

بالنسبة إلى حزمة تديرها وحدة، يعرض journalctl -u myapp.service -b --no-pager ناتج docker compose الدقيق من الإقلاع، بما في ذلك فشل سحب صورة أو فقدان ملف .env.

أمور تتسبب بهدوء في تعطل التشغيل التلقائي

الحاويات التي تُنشأ باستخدام docker compose run لا تحصل مطلقًا على سياسة إعادة التشغيل من الملف. يتعامل Compose معها كحاويات تُنشأ لمرة واحدة. إذا بدت إحدى الخدمات وكأنها تتجاهل سياستها، فتحقق مما إذا كانت قد بدأت باستخدام run بدلًا من up.

يُحل المسار النسبي في وحدة تخزين أو في إدخال env_file نسبةً إلى دليل ملف Compose. يعمل ذلك من الصدفة، ويعمل أيضًا من وحدة تضبط WorkingDirectory. لكنه يفشل من وحدة لا تضبطه، لأن دليل العمل يكون عندئذٍ /.

يُعد Docker دون root حالة منفصلة. يعمل البرنامج الخدمي الخفي كخدمة مستخدم، وتتوقف خدمة المستخدم عند انتهاء آخر جلسة لذلك المستخدم. فعّلها للمستخدم واسمح لها بالاستمرار في العمل عند عدم تسجيل دخول أي مستخدم:

systemctl --user enable docker
sudo loginctl enable-linger $USER

من دون enable-linger، يتوقف البرنامج الخدمي الخفي دون root عند تسجيل خروجك، وتتوقف معه الحاويات. ويبدو ذلك تمامًا كأنه تعطل في سياسة إعادة التشغيل.

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

FAQ

ما الفرق بين restart: always و restart: unless-stopped؟

تعيد كلتا السياستين تشغيل الحاوية عند توقفها تلقائيًا. ويظهر الفرق بعد إيقاف الحاوية يدويًا. مع always، تبدأ الحاوية مرة أخرى عند بدء تشغيل عفريت Docker في المرة التالية، ولذلك يلغي إعادة التشغيل إيقافك اليدوي. مع unless-stopped، يتذكر العفريت أن الحاوية أُوقفت عمدًا ويتركها متوقفة. استخدم unless-stopped ما لم تكن تريد تحديدًا حاوية لا تبقى متوقفة.

أضفت restart: unless-stopped، لكن الحاوية لا تبدأ بعد إعادة التشغيل. لماذا؟

تُحفظ السياسة على الحاوية، وليس في الملف، ولا يؤدي تعديل YAML إلى تحديث حاوية موجودة. شغّل docker compose up -d لكي يعيد Compose إنشاء الحاوية، ثم تحقّق باستخدام docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app). إذا طبع ذلك no، فهذا يعني أن الحاوية أُنشئت قبل تعديلك. والسبب الشائع الآخر هو أن docker.service غير مفعّل، ويمكنك التحقق من ذلك باستخدام systemctl is-enabled docker.

هل أحتاج إلى وحدة systemd إذا كنت أستخدم سياسات إعادة التشغيل؟

عادةً لا. تكفي سياسة إعادة التشغيل لمكدس يحتاج إلى الشبكة فقط، وهذا ينطبق على معظم المكدسات. أضف وحدة عندما تعتمد الحاويات على شيء لا يكون جاهزًا عند بدء تشغيل عفريت Docker، مثل قرص خارجي أو وحدة تخزين مشفّرة أو مشاركة NFS أو واجهة VPN. تتيح لك الوحدة ترتيب بدء التشغيل من خلال After= و RequiresMountsFor=، وهو ما لا يمكن لسياسة إعادة التشغيل التعبير عنه.

كيف أوقف مكدسًا نهائيًا من دون أن يعود عند إعادة التشغيل التالية؟

باستخدام unless-stopped، يكفي docker compose stop، لأن الحاوية التي أوقفتها يدويًا لا تُستأنف عند إعادة تشغيل العفريت. أما مع always، فلا يكفي الإيقاف، وتعود الحاوية بعد إعادة التشغيل. شغّل docker compose down، الذي يزيل الحاويات، أو غيّر السياسة أولًا باستخدام docker update --restart no my-container. إذا كانت وحدة systemd تدير المكدس، فشغّل sudo systemctl disable myapp.service أيضًا، وإلا فستبدأ الوحدة تشغيله مرة أخرى.