ความแตกต่างระหว่าง command และ entrypoint ใน Docker
ทำความเข้าใจการทำงานของ entrypoint และ command ใน Docker Compose พร้อมวิธีเขียนทับค่าเริ่มต้นอย่างถูกต้อง เพื่อป้องกันปัญหาการตั้งค่าทับซ้อนที่ทำให้คอนเทนเนอร์ไม่ทำงาน
ความแตกต่างระหว่าง command และ entrypoint ใน Docker Compose ในกฎเดียว
ใน Docker Compose นั้น entrypoint: ใช้กำหนดโปรแกรมที่จะทำงาน และ command: ใช้กำหนดอาร์กิวเมนต์ที่ส่งให้กับโปรแกรมนั้น กระบวนการทำงานของคอนเทนเนอร์คือรายการ entrypoint ที่ต่อท้ายด้วยรายการ command พฤติกรรมอื่นๆ ทั้งหมดในหน้านี้เป็นผลสืบเนื่องมาจากประโยคนี้เพียงประโยคเดียว
คีย์ทั้งสองนี้สอดคล้องกับคำสั่งใน Dockerfile สองคำสั่ง โดย entrypoint: จะเข้าไปแทนที่ ENTRYPOINT ของอิมเมจ และ command: จะเข้าไปแทนที่ CMD ของอิมเมจ ทั้งสองส่วนนี้ไม่ได้ทำงานแยกจากกันอย่างอิสระ ซึ่งเป็นจุดที่ผู้ใช้งานมักเกิดปัญหา คือการตั้งค่า entrypoint: จะทำให้ CMD ของอิมเมจถูกยกเลิกไป ข้อกำหนดของ Compose ระบุเรื่องนี้ไว้อย่างชัดเจนว่า หาก entrypoint ไม่เป็นค่าว่าง Compose จะเพิกเฉยต่อ command เริ่มต้นใดๆ ที่มาจากอิมเมจนั้น
อ่านสิ่งที่อิมเมจประกาศไว้แล้ว
ก่อนที่คุณจะเขียนทับการตั้งค่าใดๆ ให้ตรวจสอบสิ่งที่อิมเมจนั้นมาให้
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 และสุดท้ายจะดำเนินการตามอาร์กิวเมนต์ที่ได้รับ การทราบว่าคุณต้องการเปลี่ยนแปลงส่วนใดคือหัวใจสำคัญของการตัดสินใจ หากต้องการส่ง flag ไปยังฐานข้อมูล คุณต้องแทนที่ command: หากคุณแทนที่ entrypoint: กระบวนการตั้งค่าทั้งหมดจะไม่ทำงานเลย
การรวมกันทั้งสี่รูปแบบที่แสดงในภาพขนาดเล็ก
สร้างอิมเมจที่มีหน้าที่เพียงอย่างเดียวคือพิมพ์รายการอาร์กิวเมนต์ที่ใช้ในการเริ่มต้นโปรเซส
FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]docker build -t argdemo .services:
demo:
image: argdemoรัน docker compose up หลังจากแก้ไขแต่ละครั้ง และอ่านบรรทัดเดียวที่บันทึกไว้ใน log
- ไม่ได้ตั้งค่าคีย์ใดเลย โปรเซสคือ
/bin/echo ep cmdและ log แสดงep cmd - ตั้งค่าเฉพาะ
command: ["cmd2"]โปรเซสคือ/bin/echo ep cmd2โดยที่ entrypoint จะไม่ถูกเปลี่ยนแปลงและมีเพียงอาร์กิวเมนต์เท่านั้นที่เปลี่ยนไป - ตั้งค่าเฉพาะ
entrypoint: ["/bin/echo", "ep2"]โปรเซสคือ/bin/echo ep2และ log แสดงep2โดยcmdจากอิมเมจจะหายไปและไม่มีการแจ้งเตือนใดๆ - ตั้งค่าทั้งสองคีย์ โปรเซสคือ
/bin/echo ep2 cmd2นี่เป็นกรณีเดียวที่คุณสามารถควบคุมรายการอาร์กิวเมนต์ทั้งหมดได้
เหตุใดการตั้งค่า entrypoint จึงล้างค่า CMD ของอิมเมจ
ค่า CMD ของอิมเมจถูกเขียนไว้เป็นรายการอาร์กิวเมนต์เริ่มต้นสำหรับ ENTRYPOINT ของอิมเมจนั้น เมื่อคุณแทนที่ 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 ส่งผลให้ exec ไม่ทำงาน สคริปต์ดำเนินไปจนถึงบรรทัดสุดท้าย และคอนเทนเนอร์จะหยุดทำงานด้วยรหัส 0 โดยไม่มีข้อความแสดงข้อผิดพลาดใดๆ คุณต้องใส่อาร์กิวเมนต์กลับเข้าไปด้วยตนเอง:
services:
web:
image: nginx:1.27
entrypoint: /custom-init.sh
command: ["nginx", "-g", "daemon off;"]กฎที่ควรจำไว้คือ: ทุกครั้งที่คุณตั้งค่า entrypoint: ให้ตัดสินใจด้วยว่า command: ควรเป็นค่าใดในการแก้ไขครั้งเดียวกันนั้น
รูปแบบ Exec และรูปแบบ Shell รวมถึงความแตกต่างใน Compose
Dockerfile รองรับไวยากรณ์ 2 รูปแบบ CMD ["nginx", "-g", "daemon off;"] คือรูปแบบ exec: โปรแกรมไบนารีจะทำงานโดยตรงโดยไม่มี shell เข้ามาเกี่ยวข้อง CMD nginx -g "daemon off;" คือรูปแบบ shell: Docker จะเขียนคำสั่งใหม่เป็น /bin/sh -c 'nginx -g "daemon off;"' ทำให้ shell ทำงานก่อนและโปรแกรมของคุณจะกลายเป็น child process ของ shell นั้น
Compose ไม่ได้ใช้กฎเดียวกันนี้ ซึ่งมักทำให้ผู้ใช้งานประหลาดใจ สตริงใน command: จะถูกแยกเป็นอาร์กิวเมนต์และทำงานโดยตรงโดยไม่มีตัวครอบเป็น /bin/sh -c เอกสารอ้างอิงของ Compose ระบุไว้อย่างชัดเจนว่าฟิลด์ command จะไม่ทำงานภายใต้บริบทของ SHELL ที่กำหนดไว้ใน image ดังนั้นหากคุณต้องการคุณสมบัติของ shell คุณต้องเรียกใช้ shell ด้วยตนเอง
นี่คือเหตุผลที่ command: echo "hello $$HOSTNAME" แสดงผลข้อความตัวอักษร hello $HOSTNAME ออกมา เนื่องจากไม่มี shell ใดได้รับสตริงนี้ไปประมวลผล จึงไม่มีการขยายค่าตัวแปรใดๆ หากคุณต้องการใช้งาน shell ให้เรียกใช้ shell โดยตรง:
services:
demo:
image: alpine:3.20
command: /bin/sh -c 'echo "hello $$HOSTNAME"'สัญญาณ, PID 1 และการทำ docker compose down อย่างสะอาด
docker compose stop และ docker compose down จะส่ง SIGTERM ไปยัง PID 1 ภายในแต่ละคอนเทนเนอร์ รอเป็นเวลา stop_grace_period จากนั้นจึงส่ง SIGKILL โดยระยะเวลาผ่อนผัน (grace period) เริ่มต้นคือ 10 วินาที
PID 1 มีความพิเศษใน Linux เคอร์เนลจะไม่ใช้การกระทำเริ่มต้นของสัญญาณกับ PID 1 ดังนั้นกระบวนการที่ไม่ได้ติดตั้งตัวจัดการ SIGTERM จะเพิกเฉยต่อ SIGTERM เมื่อทำงานเป็น PID 1 มันจะค้างอยู่จนครบระยะเวลาผ่อนผันแล้วจึงถูกสั่ง kill ทันที ซึ่งจะทำให้การเชื่อมต่อที่เปิดอยู่หรือธุรกรรมที่ยังไม่เสร็จสมบูรณ์ถูกตัดขาด
การมีเชลล์อยู่หน้าโปรแกรมของคุณทำให้เหตุการณ์นี้เกิดขึ้นได้ง่ายขึ้น เพราะเชลล์จะเป็น PID 1 และเชลล์ส่วนใหญ่ไม่ส่งต่อสัญญาณไปยังกระบวนการลูก เชลล์บางตัวจะแทนที่ตัวเองด้วยคำสั่งสุดท้ายในสตริง -c ดังนั้นบางครั้งโปรแกรมของคุณอาจได้รับ PID 1 อยู่ดี ทั้งนี้ขึ้นอยู่กับเชลล์และสตริงที่ใช้ ดังนั้นอย่าคาดเดา ให้ตรวจสอบด้วยคำสั่ง:
docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echoหาก PID 1 แสดงผลเป็น /bin/sh -c ... แทนที่จะเป็นโปรแกรมของคุณ มีวิธีแก้ไขสองวิธี คือใช้รูปแบบ exec ในอิมเมจ หรือคงเชลล์ไว้แล้วส่งมอบกระบวนการด้วย exec:
services:
web:
image: myapp:1.4
command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'exec จะแทนที่กระบวนการเชลล์ด้วยโปรแกรมของคุณแทนการสร้างกระบวนการลูก (fork) ดังนั้นโปรแกรมของคุณจะได้รับ PID 1 และได้รับสัญญาณโดยตรง
โปรแกรมบางตัวสร้างกระบวนการลูกขึ้นมาแล้วไม่ยอมเก็บกวาด ทำให้เกิดกระบวนการซอมบี้ (zombie processes) เนื่องจาก PID 1 มีหน้าที่เป็นผู้เก็บกวาด (reaper) ด้วย Compose มีสวิตช์สำหรับจัดการเรื่องนี้:
services:
web:
image: myapp:1.4
init: true
stop_grace_period: 30sinit: 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 ยังปรากฏในอีกจุดหนึ่ง คือ healthcheck ที่เขียนแบบ test: ["CMD", "curl", "-f", "http://localhost/"] จะรันไบนารีโดยตรง ในขณะที่ test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] จะรันผ่านเชลล์เพื่อให้ || มีความหมาย การเขียน healthcheck ใน Compose ที่ล้มเหลวอย่างตรงไปตรงมา ครอบคลุมรายละเอียดที่เหลือของหัวข้อนี้
การเพิ่ม flag ให้กับ official image
นี่คือสิ่งที่ผู้อ่านส่วนใหญ่ต้องการ คุณต้องการเพิ่ม flag พิเศษหนึ่งรายการให้กับ postgres โดยไม่ให้กระทบต่อสคริปต์เริ่มต้น (initialisation script)
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 ด้วยไฟล์ override ทั้งหมด ไม่ใช่การต่อท้าย ดังนั้นไฟล์ที่สองที่มีการตั้งค่า command: จะเข้ามาแทนที่ค่าเดิมโดยไม่มีการแจ้งเตือน
ค่า ${POSTGRES_PASSWORD} ด้านบนจะถูกขยายโดย Compose บนโฮสต์จากไฟล์ .env ของคุณ ก่อนที่คอนเทนเนอร์จะถูกสร้างขึ้น หัวข้อ ไฟล์ environment และ secret ใน 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เข้าไป ดังนั้นจึงไม่เกิดการชนกันกับบริการที่กำลังทำงานอยู่ - บริการที่เป็น dependency จะเริ่มทำงานก่อน สิ่งใดก็ตามที่อยู่ใน
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
เหตุใดคอนเทนเนอร์ของฉันจึงหยุดทำงานทันที
ให้เริ่มตรวจสอบจาก exit code เพราะจะช่วยจำกัดขอบเขตสาเหตุได้อย่างรวดเร็ว
docker compose ps -a
docker compose logs appExit code 0 และไม่มีข้อความแสดงผล คำสั่งทำงานเสร็จสิ้นแล้ว สาเหตุที่พบบ่อยที่สุดคือการใช้ entrypoint: ไปเขียนทับค่าเดิม ซึ่งทำให้ CMD ของอิมเมจหายไปด้วย ส่งผลให้ entrypoint ทำงานโดยไม่มีรายการอาร์กิวเมนต์และไม่มีคำสั่งให้ดำเนินการต่อ
ข้อผิดพลาดที่ลงท้ายด้วย permission denied สคริปต์ไม่มีสิทธิ์การทำงาน (executable bit) ภายในอิมเมจ ซึ่งมักเกิดจากการที่ไม่ได้ตั้งค่าสิทธิ์นี้ไว้ในไฟล์ตั้งแต่ใน repository ให้ตั้งค่าสิทธิ์ดังกล่าวในขั้นตอน build ด้วยคำสั่ง COPY --chmod=0755 entrypoint.sh /entrypoint.sh
ข้อผิดพลาดที่ลงท้ายด้วย no such file or directory สำหรับไฟล์ที่คุณเห็นว่ามีอยู่จริงในอิมเมจ สคริปต์ดังกล่าวใช้รูปแบบการขึ้นบรรทัดใหม่แบบ Windows ทำให้บรรทัดแรกอ่านค่าเป็น #!/bin/sh ตามด้วยไบต์ carriage return ส่งผลให้ kernel ค้นหาตัวแปลภาษา (interpreter) ที่มีชื่อรวมไบต์ดังกล่าวซึ่งไม่มีอยู่จริง ให้รันคำสั่ง dos2unix entrypoint.sh จากนั้นเพิ่ม * text eol=lf ลงใน .gitattributes เพื่อป้องกันไม่ให้ปัญหานี้เกิดขึ้นอีก
executable file not found in $PATH ไฟล์ไบนารีที่ระบุใน command: ไม่มีอยู่ในอิมเมจ หรือคุณเขียนคำสั่ง shell built-in เช่น 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เมื่อคุณต้องการให้คอนเทนเนอร์ทำงานค้างไว้เพื่อให้สามารถแนบ (attach) เข้าไปได้หลายครั้ง ให้สั่งให้มันรันกระบวนการที่ไม่สิ้นสุด ให้ใส่คำสั่งนี้ไว้ในไฟล์ override ที่คุณไม่ได้ commit:
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 แรกของคุณ การสร้าง Compose stack แรกบน VPS จะครอบคลุมถึงโครงสร้างไฟล์ที่ทุกขั้นตอนข้างต้นอ้างอิงถึง
FAQ
ทำไมคอนเทนเนอร์ของฉันถึงหยุดทำงานทันทีหลังจากรัน docker compose up?
ให้ตรวจสอบ docker compose ps -a เพื่อดูรหัสสถานะการหยุดทำงาน (exit code) หากเป็น 0 โดยไม่มีข้อความแสดงผล มักหมายความว่าคุณได้ตั้งค่า entrypoint: ในบริการนั้น ซึ่งเป็นการล้างค่า CMD ของอิมเมจไปด้วย ส่งผลให้ entrypoint ทำงานโดยไม่มีรายการอาร์กิวเมนต์และจบการทำงานทันที ให้เพิ่มอาร์กิวเมนต์กลับเข้าไปด้วย command: หากพบข้อผิดพลาดที่ลงท้ายด้วย permission denied หมายความว่าสคริปต์ entrypoint ไม่มีสิทธิ์ในการประมวลผล (executable bit) หากพบข้อผิดพลาดที่ลงท้ายด้วย no such file or directory สำหรับไฟล์ที่มีอยู่จริง แสดงว่าสคริปต์นั้นใช้รูปแบบบรรทัดแบบ Windows ทำให้บรรทัด shebang เรียกใช้ตัวแปลภาษา (interpreter) ที่ไม่มีอยู่จริง
การตั้งค่า entrypoint ใน Compose จะลบ CMD ของอิมเมจหรือไม่?
ใช่ หาก entrypoint มีค่า Compose จะเพิกเฉยต่อคำสั่งเริ่มต้นที่ประกาศไว้ในอิมเมจ นี่เป็นพฤติกรรมตามเอกสารและสอดคล้องกับ docker run --entrypoint เหตุผลคือ CMD ของอิมเมจถูกเขียนขึ้นเพื่อเป็นอาร์กิวเมนต์สำหรับ ENTRYPOINT ของอิมเมจนั้น ดังนั้นเมื่อคุณแทนที่ entrypoint อาร์กิวเมนต์เดิมจึงไม่มีคำสั่งรองรับอีกต่อไป ให้ตั้งค่า command: ในบริการเดียวกันหาก entrypoint ใหม่ยังคงต้องการอาร์กิวเมนต์
สตริงในคำสั่ง Compose จะถูกรันผ่านเชลล์หรือไม่?
ไม่ ต่างจาก CMD ใน Dockerfile สตริงใน command: ของ Compose จะถูกแยกเป็นอาร์กิวเมนต์และรันโดยตรงโดยไม่มีตัวครอบ /bin/sh -c ดังนั้น $VARIABLE จะไม่ถูกขยายโดยเชลล์ภายในคอนเทนเนอร์ คุณต้องเรียกใช้เชลล์ด้วยตนเองเมื่อต้องการใช้งาน เช่นใน command: /bin/sh -c 'echo "hello $$HOSTNAME"' การใช้ $$ ซ้ำสองครั้งเป็นการหลีก (escape) เครื่องหมายดอลลาร์ เพื่อให้ Compose ส่งผ่านไปยังคอนเทนเนอร์แทนที่จะขยายค่าบนโฮสต์
ทำไม docker compose down ถึงใช้เวลาสิบวินาทีก่อนจะหยุดคอนเทนเนอร์?
Compose จะส่ง SIGTERM ไปยัง PID 1 และรอเป็นเวลา stop_grace_period (ค่าเริ่มต้นคือ 10 วินาที) จากนั้นจึงส่ง SIGKILL เคอร์เนลจะไม่ใช้การจัดการสัญญาณเริ่มต้นกับ PID 1 ดังนั้นโปรแกรมที่ไม่มีตัวจัดการสัญญาณ SIGTERM จะเพิกเฉยต่อสัญญาณและรอจนครบเวลาเสมอ ให้ตรวจสอบว่า PID 1 คืออะไรด้วย docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' ' หากเป็นเชลล์ ให้เปลี่ยนอิมเมจไปใช้รูปแบบ exec หรือเขียน exec ไว้ภายในสตริงเชลล์ หากกระบวนการมีการสร้างกระบวนการลูก (child process) ที่ไม่เคยถูกเก็บกวาด ให้ตั้งค่า init: true ในบริการนั้น