SSD Nodes Learn 🎉 VPS من $4.99/شهر
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-07

Docker Compose: الفرق بين command و entrypoint

اعرف أيهما يشغّل البرنامج وأيهما يمرّر الوسائط، وشاهد التركيبات الأربع في Compose ولماذا يؤدي ضبط entrypoint إلى تجاهل CMD الافتراضي من الصورة.

مقارنة أمر Docker Compose بنقطة الدخول، في قاعدة واحدة

في Docker Compose، يحدد entrypoint: البرنامج الذي يعمل، بينما يحدد command: الوسائط التي تُمرَّر إلى ذلك البرنامج. تكون عملية الحاوية هي قائمة نقطة الدخول، مع إلحاق قائمة الأمر بنهايتها. وكل سلوك آخر في هذه الصفحة ينتج عن هذه الجملة.

يتوافق هذان المفتاحان مع تعليمتين في Dockerfile. يستبدل entrypoint: قيمة ENTRYPOINT الموجودة في الصورة. ويستبدل command: قيمة CMD الموجودة في الصورة. وهما ليسا مستقلين، وهذا هو سبب تعثر المستخدمين: يؤدي ضبط entrypoint: أيضاً إلى تجاهل قيمة CMD الموجودة في الصورة. وتنص مواصفة Compose على ذلك مباشرة. إذا كانت قيمة entrypoint غير فارغة، يتجاهل Compose الأمر الافتراضي من الصورة.

اقرأ ما يعلنه image مسبقاً

قبل أن تتجاوز أي إعداد، افحص ما يأتي مع image.

docker image inspect --format '{{json .Config.Entrypoint}}' postgres:16
docker image inspect --format '{{json .Config.Cmd}}' postgres:16

تحصل على ["docker-entrypoint.sh"] و["postgres"]، ولذلك تشغّل الحاوية docker-entrypoint.sh postgres. ينشئ ذلك السكربت مجلد البيانات عند الإقلاع الأول، ويقرأ متغيرات POSTGRES_*، ويخفض الامتيازات إلى المستخدم postgres، ثم ينفّذ الوسائط التي أُعطيَت له. تحديد الجزء الذي تريد تغييره هو جوهر القرار. لتمرير خيار إلى قاعدة البيانات، استبدل command:. أما إذا استبدلت entrypoint:، فلن تُنفَّذ أي من خطوات الإعداد هذه.

التركيبات الأربع، موضحة في صورة صغيرة

أنشئ صورة تقتصر مهمتها على طباعة قائمة الوسائط التي بدأت بها.

FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]
docker build -t argdemo .
services:
  demo:
    image: argdemo

شغّل docker compose up بعد كل تعديل، واقرأ السطر الوحيد الذي تسجله.

  • لم يُضبط أي مفتاح. تكون العملية /bin/echo ep cmd، ويعرض السجل ep cmd.
  • command: ["cmd2"] فقط. تكون العملية /bin/echo ep cmd2. يبقى entrypoint دون تغيير، ولا تتغير إلا الوسائط.
  • entrypoint: ["/bin/echo", "ep2"] فقط. تكون العملية /bin/echo ep2، ويعرض السجل ep2. يختفي cmd من الصورة، ولا يحذّرك أي شيء من ذلك.
  • ضُبط كلا المفتاحين. تكون العملية /bin/echo ep2 cmd2. هذه هي الحالة الوحيدة التي تتحكم فيها بقائمة الوسائط كاملة.

لماذا يؤدي ضبط entrypoint إلى مسح CMD من الصورة

تُكتب CMD الخاصة بالصورة بوصفها قائمة الوسائط الافتراضية لـENTRYPOINT الخاص بها. عند استبدال نقطة الدخول، تصبح هذه الوسائط تابعة لبرنامج لم يعد قيد التشغيل، لذلك يحذفها Compose بدلاً من إنشاء سطر أوامر لم يقصده مؤلف الصورة. ويتصرف docker run --entrypoint بالطريقة نفسها، لذلك فهذا سلوك Docker وليس مشكلة خاصة بـCompose.

والنتيجة عملية ومحددة. يعرّف nginx:1.27 كلاً من ENTRYPOINT ["/docker-entrypoint.sh"] وCMD ["nginx", "-g", "daemon off;"]. عند ضبط entrypoint: /custom-init.sh، يبدأ البرنامج النصي بقائمة وسائط فارغة. والبرنامج النصي الذي ينتهي عادةً بـexec "$@" لا يجد شيئاً ينفذه، لذلك لا يفعل exec شيئاً، ويصل البرنامج النصي إلى سطره الأخير، ثم تنتهي الحاوية بالرمز 0 من دون أي رسالة خطأ.

أعد الوسائط بنفسك:

services:
  web:
    image: nginx:1.27
    entrypoint: /custom-init.sh
    command: ["nginx", "-g", "daemon off;"]

القاعدة الأساسية: كلما ضبطت entrypoint:، حدّد في التعديل نفسه القيمة التي ينبغي أن تكون لـcommand:.

صيغة exec وصيغة shell، والفرق الذي يضيفه Compose

يقبل Dockerfile صياغتين. CMD ["nginx", "-g", "daemon off;"] هي صيغة exec: يُشغَّل الملف التنفيذي مباشرةً من دون استخدام shell. أما CMD nginx -g "daemon off;" فهي صيغة shell: يعيد Docker كتابتها بالشكل /bin/sh -c 'nginx -g "daemon off;"'، لذلك يُشغَّل shell أولاً ويصبح برنامجك عمليةً فرعيةً له.

لا يطبّق Compose هذه القاعدة، وهذا ما يفاجئ بعض المستخدمين. تُقسَّم السلسلة النصية في command: إلى وسيطات وتُنفَّذ مباشرةً، من دون غلاف /bin/sh -c. يوضح مرجع Compose ذلك صراحةً: لا يُشغَّل الحقل command ضمن سياق SHELL المحدد في الصورة، لذلك يجب استدعاء shell بنفسك إذا كنت تحتاج إلى ميزاته.

لهذا يطبع command: echo "hello $$HOSTNAME" النص الحرفي hello $HOSTNAME. لم يقرأ shell السلسلة النصية قط، لذلك لم يُوسّع أي جزء منها. اطلب استخدام shell عندما تحتاج إليه:

services:
  demo:
    image: alpine:3.20
    command: /bin/sh -c 'echo "hello $$HOSTNAME"'

الإشارات وPID 1 وإيقاف docker compose بطريقة سليمة

يرسل docker compose stop وdocker compose down الإشارة SIGTERM إلى PID 1 داخل كل حاوية، وينتظران stop_grace_period، ثم يرسلان SIGKILL. مدة السماح الافتراضية هي 10 ثوانٍ.

يتميّز PID 1 بسلوك خاص في Linux. لا تطبّق النواة الإجراء الافتراضي للإشارة على PID 1، لذلك تتجاهل العملية التي لا تثبّت معالج SIGTERM الإشارة SIGTERM ببساطة عندما تعمل بصفتها PID 1. وتبقى قيد التشغيل طوال مدة السماح، ثم تُقتل مباشرة، ما يؤدي إلى قطع أي اتصال مفتوح أو معاملة لم تُثبَّت بعد.

يجعل وضع shell أمام برنامجك حدوث ذلك أكثر احتمالاً، لأن shell يكون PID 1، ومعظم برامج shell لا تمرّر الإشارات إلى عملية فرعية. تستبدل بعض برامج shell نفسها بالأمر النهائي في سلسلة -c، لذلك قد يصل برنامجك إلى PID 1 في بعض الحالات. يعتمد ذلك على shell وعلى السلسلة المحددة، لذلك لا تخمّن. اقرأها:

docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echo

إذا ظهر PID 1 على أنه /bin/sh -c ... بدلاً من برنامجك، فهناك إصلاحان. استخدم صيغة exec في الصورة، أو أبقِ shell ومرّر العملية باستخدام exec:

services:
  web:
    image: myapp:1.4
    command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'

يستبدل exec عملية shell ببرنامجك بدلاً من إنشاء عملية فرعية، لذلك يرث برنامجك PID 1 ويتلقى الإشارة.

تنشئ بعض البرامج عمليات فرعية ولا تجمعها أبداً، ما يترك عمليات زومبي، لأن PID 1 هو أيضاً جامع العمليات. يوفّر Compose خياراً لذلك:

services:
  web:
    image: myapp:1.4
    init: true
    stop_grace_period: 30s

يشغّل init: true عملية init صغيرة بصفتها PID 1، ويمرّر الإشارات إلى عمليتك، ويجمع العمليات الفرعية. يمنح stop_grace_period عملية الإيقاف البطيئة فعلياً وقتاً إضافياً. إذا كان برنامجك يتوقع إشارة مختلفة، يغيّر stop_signal: SIGQUIT الإشارة التي يرسلها Compose. اقرأ ما تطلبه الصورة مسبقاً باستخدام docker image inspect --format '{{.Config.StopSignal}}' nginx:1.27.

إذا كان المكدس يستغرق دائماً عشر ثوانٍ لكل خدمة عند استخدام docker compose down، فهذا يعني أن شيئاً لا يعالج SIGTERM. أصلح ذلك قبل إلقاء اللوم على الأدوات، وراجع الفرق بين docker compose down وstop لمعرفة ما يزيله كل أمر فرعي.

يظهر الفرق نفسه بين exec وshell في موضع آخر. يعمل فحص السلامة المكتوب بصيغة test: ["CMD", "curl", "-f", "http://localhost/"] على تشغيل الملف التنفيذي مباشرة، بينما يعمل test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] عبر shell لكي تكون || ذات معنى. يشرح كتابة فحوصات سلامة Compose التي تفشل بصدق بقية هذا الحقل.

إضافة خيار إلى صورة رسمية

هذا هو ما جاء معظم القراء من أجله. تريد إضافة خيار واحد إلى postgres، ويجب ألا تؤثر في برنامج التهيئة الأولي.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    command: postgres -c max_connections=200 -c shared_buffers=256MB

volumes:
  pgdata:

تغيّر command: فقط، لذلك يستمر docker-entrypoint.sh في العمل، ويستمر في تنفيذ ما مررته إليه. تحقّق من النتيجة بدلاً من افتراضها:

docker compose up -d db
docker compose exec -T db psql -U postgres -c 'show max_connections;'

يجب أن يُظهر الخرج 200. إذا كان لا يزال يُظهر 100، فشغّل docker compose config وتأكد من أن command المتوقعة موجودة في الخرج المدمج. يدمج Compose ملفات التجاوز باستبدال command بالكامل، وليس بإضافة محتواها إليه. لذلك، يفوز ملف ثانٍ يضبط command: بصمت.

يُوسّع Compose ${POSTGRES_PASSWORD} أعلاه على المضيف من ملف .env، قبل إنشاء الحاوية. يوضّح ملفات البيئة والأسرار في Compose المكان الآمن لتخزين هذه القيمة.

تشغيل عملية ترحيل لمرة واحدة باستخدام docker compose run

docker compose run ينشئ حاوية جديدة من تعريف الخدمة نفسه، ويستبدل الأمر بأي شيء تكتبه بعد اسم الخدمة. يظل entrypoint للصورة قيد التشغيل، لذلك تُجهَّز الحاوية بالطريقة نفسها تماماً مثل الحاوية طويلة التشغيل.

docker compose run --rm app python manage.py migrate
  • --rm يحذف الحاوية عند انتهاء الأمر. بدونه، يترك كل تشغيل حاوية متوقفة، ويمكن رؤيتها في docker compose ps -a.
  • لا تُنشر المنافذ. تتجاهل حاوية run إعداد ports: الخاص بالخدمة ما لم تضف --service-ports، لذلك لا يمكن أن تتعارض مع الخدمة قيد التشغيل.
  • تبدأ التبعيات أولاً. يبدأ كل ما يرد في depends_on قبل تنفيذ أمرك، بينما يتجاوز --no-deps ذلك.
  • تحصل الحاوية على اسم مُنشأ تلقائياً مثل myproject-app-run-9f2c1a، لذلك لا يتعارض اسمها أبداً مع اسم حاوية الخدمة.

لاستبدال entrypoint أيضاً، استخدم هذا الخيار:

docker compose run --rm --entrypoint /bin/sh app -c 'python manage.py migrate'

تكون قائمة الوسائط الناتجة /bin/sh -c 'python manage.py migrate'، لأن الكلمات التي تأتي بعد اسم الخدمة تظل هي الأمر. أما docker compose exec فهي الأداة الأخرى، وتعمل بطريقة مختلفة: فهي تشغّل عملية داخل حاوية قيد التشغيل بالفعل، وتتجاهل كلاً من entrypoint: وcommand: تماماً. استخدم run لمهمة تحتاج إلى حاوية جديدة، واستخدم exec لفحص حاوية قيد التشغيل. يضع مرجع أوامر Compose بقية الأوامر الفرعية جنباً إلى جنب.

لماذا تتوقف حاويتي مباشرة؟

ابدأ برمز الخروج، لأنه يضيّق نطاق السبب بسرعة.

docker compose ps -a
docker compose logs app

رمز الخروج 0 من دون مخرجات. نُفّذ الأمر وانتهى. السبب الأكثر شيوعاً هو تجاوز entrypoint: الذي أزال أيضاً CMD الخاص بالصورة، لذلك شُغِّلت نقطة الدخول بقائمة وسيطات فارغة ولم تجد ما تسلّمه.

خطأ ينتهي بـ permission denied. لا يحتوي البرنامج النصي على بت التنفيذ داخل الصورة، ويحدث ذلك عادةً لأن البت لم يُضبط على الملف في المستودع. اضبطه وقت البناء باستخدام COPY --chmod=0755 entrypoint.sh /entrypoint.sh.

خطأ ينتهي بـ no such file or directory لملف يمكنك رؤيته بوضوح داخل الصورة. يستخدم البرنامج النصي نهايات أسطر بنمط Windows. لذلك يُقرأ سطره الأول على أنه #!/bin/sh متبوعاً ببايت إرجاع، فيبحث kernel عن مفسّر يتضمن هذا البايت في اسمه ولا يجده. شغّل dos2unix entrypoint.sh، ثم أضف * text eol=lf إلى .gitattributes حتى لا تتكرر المشكلة.

executable file not found in $PATH. الملف التنفيذي المذكور في command: غير موجود في الصورة، أو كتبت أمراً مضمّناً في shell مثل cd في موضع لا يقبل إلا برنامجاً فعلياً.

الوصول إلى shell داخل صورة يفشل entrypoint فيها

عندما يتوقف entrypoint قبل أن تتمكن من فحص أي شيء، استبدله:

docker compose run --rm --entrypoint /bin/sh app

إذا أعاد ذلك executable file not found in $PATH، فلا تحتوي الصورة على shell إطلاقاً. غالباً لا تتضمن الصور المبنية على Distroless وscratch أي shell. مع ذلك، يمكنك قراءة نظام الملفات من الخارج دون تشغيل entrypoint:

docker create --name probe myapp:1.4
docker export probe | tar -tv | head -40
docker rm probe

عندما تحتاج إلى إبقاء الحاوية قيد التشغيل حتى تتمكن من الاتصال بها مراراً، شغّلها على عملية لا تتوقف أبداً. ضع ذلك في ملف override لا تلتزم به في نظام التحكم بالإصدارات:

services:
  app:
    entrypoint: ["tail", "-f", "/dev/null"]
    command: []

لا يُعد command: [] ضرورياً تماماً، لأن ضبط entrypoint: أزال بالفعل CMD الخاص بالصورة، لكن كتابته يسجل القصد لمن يقرأ الملف لاحقاً. شغّلها وادخل إليها:

docker compose -f compose.yaml -f compose.debug.yaml up -d app
docker compose exec app /bin/sh

شغّل الآن entrypoint الفعلي يدوياً وراقب الموضع الذي يتوقف عنده. يعرض ذلك رسالة الخطأ في الطرفية بدلاً من عرضها داخل حاوية توقفت قبل نصف ثانية. إذا كنت لا تزال تجهّز أول stack لك، يشرح إنشاء أول stack لـ Compose على VPS بنية الملفات التي يفترضها كل ما سبق.

FAQ

لماذا تتوقف الحاوية فوراً بعد تشغيل docker compose up؟

تحقق من docker compose ps -a لمعرفة رمز الخروج. يعني الخروج بالرمز 0 من دون مخرجات غالباً أنك عيّنت entrypoint: للخدمة، ما أدى أيضاً إلى مسح CMD للصورة. لذلك شُغّل entrypoint بقائمة وسائط فارغة ثم انتهى. أعد الوسائط باستخدام command:. يعني الخطأ الذي ينتهي بـpermission denied أن نص entrypoint لا يملك بت التنفيذ. أما الخطأ الذي ينتهي بـno such file or directory لملف موجود، فيعني أن النص يستخدم نهايات أسطر بنمط Windows، ولذلك يحدد سطر shebang فيه مفسراً غير موجود.

هل يؤدي تعيين entrypoint في Compose إلى إزالة CMD للصورة؟

نعم. إذا كانت entrypoint غير فارغة، يتجاهل Compose الأمر الافتراضي الذي تعلنه الصورة. هذا سلوك موثّق، ويتوافق مع docker run --entrypoint. السبب هو أن CMD للصورة يُكتب على شكل وسائط لـENTRYPOINT الخاصة بها. لذلك، عند استبدال entrypoint، لا تعود الوسائط القديمة مرتبطة بأي شيء. عيّن command: في الخدمة نفسها إذا كان entrypoint الجديد يحتاج إلى وسائط.

هل تُشغَّل السلسلة النصية في command ضمن Compose عبر shell؟

لا. بخلاف CMD في Dockerfile، تُقسَّم السلسلة النصية في command: ضمن Compose إلى وسائط وتُنفَّذ مباشرة، من دون غلاف /bin/sh -c. لذلك لا يوسّع shell داخل الحاوية $VARIABLE أبداً. استدعِ shell بنفسك عند الحاجة، كما في command: /bin/sh -c 'echo "hello $$HOSTNAME"'. تؤدي $$ المزدوجة إلى إلغاء معنى علامة الدولار، ولذلك يمررها Compose إلى الحاوية بدلاً من توسيعها على المضيف.

لماذا يستغرق docker compose down عشر ثوانٍ لحاوية واحدة؟

يرسل Compose SIGTERM إلى PID 1، وينتظر stop_grace_period (10 ثوانٍ افتراضياً)، ثم يرسل SIGKILL. لا يطبّق kernel إجراءات الإشارة الافتراضية على PID 1، لذلك يتجاهل البرنامج الذي لا يملك معالج SIGTERM الإشارة وينتظر دائماً حتى انتهاء الفترة كاملة. اعرف ماهية PID 1 فعلياً باستخدام docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' '. إذا كان shell، فغيّر الصورة إلى صيغة exec أو اكتب exec داخل سلسلة shell. إذا كانت العملية تنشئ عمليات فرعية ولا تعيد جمعها، فعيّن init: true للخدمة.

#docker-compose#entrypoint#command#containers#debugging