วิธีเข้าถึง Interactive Shell ใน Docker Compose
เรียนรู้วิธีใช้คำสั่ง docker compose exec เพื่อเข้าถึง shell ในคอนเทนเนอร์ที่กำลังทำงานอยู่ พร้อมวิธีใช้ docker compose run --rm สำหรับบริการที่ยังไม่ได้เริ่มทำงาน
เข้าถึง interactive shell ด้วย docker compose exec
docker compose exec web bash จะเปิด interactive shell ภายในคอนเทนเนอร์ที่กำลังทำงานอยู่ของบริการ web ชื่อที่ระบุหลัง exec คือชื่อบริการจากไฟล์ compose.yaml ของคุณ ไม่ใช่ชื่อคอนเทนเนอร์ หากอิมเมจไม่มี bash ให้เรียกใช้ sh แทน
docker compose ps
docker compose exec web bashให้รัน docker compose ps ก่อน คำสั่งนี้ควรแสดงรายการ web ที่มีสถานะเป็น running จากนั้นคำสั่งที่สองจะนำคุณเข้าสู่พรอมต์ภายในคอนเทนเนอร์ และใช้คำสั่ง exit หรือกด Ctrl-D เพื่อกลับออกมายังโฮสต์ บริการจะยังคงทำงานต่อไปหลังจากที่คุณออกจากคอนเทนเนอร์ เนื่องจาก exec เป็นการเริ่มกระบวนการที่สองแยกจากกระบวนการหลัก การปิด shell ของคุณจะไม่ส่งผลกระทบต่อ PID 1 (process id 1) ซึ่งเป็นกระบวนการที่คอนเทนเนอร์ถูกสร้างขึ้นมาเพื่อรัน
นี่เป็นหนึ่งในสองวิธีในการเข้าถึงคอนเทนเนอร์ โดย exec จะเป็นการเชื่อมต่อเข้าไปยังคอนเทนเนอร์ที่มีอยู่แล้ว ส่วน docker compose run จะเป็นการสร้างคอนเทนเนอร์ใหม่จากนิยามบริการเดียวกัน เนื้อหาส่วนที่เหลือเกือบทั้งหมดในคู่มือนี้ล้วนมีพื้นฐานมาจากความแตกต่างเพียงจุดเดียวนี้
เหตุใด -it จึงเป็นทางเลือกใน Compose แต่จำเป็นสำหรับ docker แบบปกติ
มีแฟล็ก 2 ตัวที่ควบคุมการโต้ตอบของเซสชัน -i ทำหน้าที่เปิด stdin ค้างไว้เพื่อให้สิ่งที่คุณพิมพ์ส่งไปถึงกระบวนการทำงานได้ ส่วน -t ทำหน้าที่จัดสรร pseudo terminal หรือที่เรียกว่า TTY เพื่อให้เชลล์แสดงพรอมต์และรองรับการใช้งานปุ่มลูกศร โดยปกติแล้ว docker exec จะปิดการทำงานทั้งสองอย่างนี้ไว้เป็นค่าเริ่มต้น จึงเป็นเหตุผลว่าทำไมตัวอย่างที่คุณเคยเห็นทั้งหมดจึงเขียนว่า docker exec -it ทั้งนี้ docker compose exec จะเปิดการทำงานทั้งสองอย่างให้คุณโดยอัตโนมัติ ดังนั้น docker compose exec -it web bash และ docker compose exec web bash จึงให้ผลลัพธ์เหมือนกัน Compose ยังคงยอมรับ -it เพื่อให้ความคุ้นเคยเดิมยังคงใช้งานได้ต่อไป
คุณจะสังเกตเห็นว่า TTY หายไปภายในเวลาไม่กี่วินาที เชลล์จะทำงานแต่ไม่แสดงพรอมต์ และคำสั่ง Ctrl-C จะไม่ส่งไปถึงกระบวนการทำงาน ในกรณีตรงกันข้ามที่คุณต้องสั่งให้ Compose ไม่ต้อง จัดสรร TTY นั้นจะมีแฟล็กเฉพาะและหัวข้ออธิบายแยกไว้ในส่วนถัดไป
สิ่งที่ควรทำเมื่ออิมเมจไม่มี bash
เมื่อคุณสั่งให้อิมเมจที่ใช้ Alpine เรียกใช้งาน bash แล้วเกิดข้อผิดพลาดดังนี้:
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH: unknownข้อความดังกล่าวไม่ใช่ปัญหาของการสั่ง exec แต่เป็นการแจ้งว่าไม่มีไฟล์ไบนารีที่คุณเรียกใช้งานอยู่ภายในอิมเมจนั้น อิมเมจ Alpine มาพร้อมกับ BusyBox ซึ่งมี ash ในรูปแบบของ /bin/sh และไม่มี bash ติดตั้งมาให้เลย ดังนั้นให้เรียกใช้งาน sh แทน:
docker compose exec web shอิมเมจที่ใช้ Debian และ Ubuntu เป็นพื้นฐาน รวมถึงแท็ก -slim จะมี bash ติดตั้งมาให้ ซึ่ง bash จะช่วยให้คุณใช้งานประวัติคำสั่ง (command history) และระบบเติมคำสั่งอัตโนมัติ (completion) ได้ดีกว่า ดังนั้นให้ลองใช้ bash ก่อนแล้วจึงเปลี่ยนไปใช้ sh หากจำเป็น โดย sh นั้นมีอยู่ในอิมเมจทั่วไปเกือบทุกประเภท
อิมเมจบางตัวไม่มี shell ติดตั้งมาให้เลย อิมเมจแบบ Distroless และอิมเมจที่สร้างด้วย FROM scratch จะบรรจุเพียงไฟล์ไบนารีของแอปพลิเคชันและไลบรารีที่จำเป็นเท่านั้น โดยเจตนาเพื่อความปลอดภัย เนื่องจาก shell ที่ไม่มีอยู่จริงย่อมไม่สามารถถูกนำมาใช้โจมตีคุณได้ ในกรณีนี้การสั่ง sh จะล้มเหลวด้วยข้อความเดียวกันและไม่มีทางเลือกอื่นให้ลองอีก มีแนวทางแก้ไข 2 วิธี คือ อิมเมจแบบ Distroless ของ Google จะมีการเผยแพร่แท็ก :debug ซึ่งเพิ่ม shell ของ BusyBox เข้ามา ดังนั้นการสลับไปใช้แท็กดังกล่าวชั่วคราวจะช่วยให้คุณเข้าไปในคอนเทนเนอร์ได้ หรืออีกวิธีคือการเริ่มคอนเทนเนอร์แยกต่างหากภายใน namespace ของเป้าหมาย:
CID=$(docker compose ps -q web)
docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshootตอนนี้คุณมีเครื่องมือของ netshoot ที่เชื่อมต่อกับเครือข่ายของแอปพลิเคชันแล้ว ดังนั้น curl localhost:8080 และ ss -lntp จะทำงานเสมือนว่าคุณอยู่ภายในคอนเทนเนอร์นั้น ระบบไฟล์ที่คุณเห็นจะเป็นของ netshoot ไม่ใช่ของแอปพลิเคชัน แต่เนื่องจากมีการใช้ process namespace ร่วมกัน การสั่ง ls /proc/1/root/ จึงสามารถเข้าถึงไฟล์ของเป้าหมายได้เมื่อคุณมีสิทธิ์เป็น root
เมื่อบริการไม่ทำงาน ให้ใช้ docker compose run --rm
คำสั่ง exec จำเป็นต้องใช้คอนเทนเนอร์ที่กำลังทำงานอยู่ หากระบุไปยังบริการที่หยุดทำงาน คำสั่งจะปฏิเสธการทำงาน:
service "web" is not runningคำสั่งดังกล่าวจะไม่เริ่มการทำงานใดๆ ให้คุณ แต่ docker compose run จะทำหน้าที่นั้น:
docker compose run --rm web bashคำสั่ง run จะสร้างคอนเทนเนอร์ใหม่จากนิยามบริการ web โดยใช้ image, environment, volumes และ networks เดียวกัน และจะแทนที่คำสั่งของบริการด้วยคำสั่งที่คุณพิมพ์ลงไป ส่วน --rm จะลบคอนเทนเนอร์นั้นทิ้งเมื่อคุณออกจากโปรแกรม หากไม่ใส่ --rm ทิ้งไว้ คอนเทนเนอร์ที่เหลือจะสะสมอยู่ภายใต้ชื่ออย่างเช่น myproject-web-run-4f1c2b ซึ่ง docker compose ps -a จะแสดงให้คุณเห็น และไม่มีกระบวนการอื่นใดมาล้างข้อมูลเหล่านี้ให้
พฤติกรรมสองอย่างของ run มักทำให้ผู้ใช้ประหลาดใจ อย่างแรกคือมันจะไม่เผยแพร่พอร์ตของบริการเว้นแต่คุณจะเพิ่ม --service-ports ซึ่งเป็นความตั้งใจในการออกแบบ เพราะหากคอนเทนเนอร์ที่สองพยายามจับจองพอร์ต 8080 ของโฮสต์ในขณะที่คอนเทนเนอร์แรกยังใช้งานอยู่ จะทำให้เกิดข้อผิดพลาด bind: address already in use อย่างที่สองคือมันจะเริ่มการทำงานทุกอย่างที่บริการระบุไว้ภายใต้ depends_on ก่อนที่ shell ของคุณจะปรากฏขึ้น ดังนั้นการเข้าไปตรวจสอบภายในเพียงครู่เดียวอาจทำให้ฐานข้อมูลและแคชเริ่มทำงานได้ ส่วน --no-deps จะข้ามขั้นตอนดังกล่าว
คำสั่ง run จะทำงานผ่าน ENTRYPOINT ของ image ในขณะที่ exec จะไม่ทำเช่นนั้น คำสั่ง exec จะเริ่มคำสั่งของคุณโดยตรงภายในคอนเทนเนอร์ที่มีอยู่ ทำให้สคริปต์ entrypoint ไม่ได้รับคำสั่งนั้น แต่ภายใต้คำสั่ง run คำสั่ง bash ของคุณจะถูกส่งไปเป็นอาร์กิวเมนต์ของสคริปต์นั้น image ทางการจำนวนมากจะลงท้าย entrypoint ด้วย exec "$@" ทำให้คำสั่งถูกส่งผ่านไปโดยตรงและคุณจะได้ shell มาใช้งาน อย่างไรก็ตาม สคริปต์ที่ตีความอาร์กิวเมนต์ของตัวเองจะจัดการกับอาร์กิวเมนต์เหล่านั้นในรูปแบบอื่น ซึ่งในกรณีนี้คุณต้องแทนที่ entrypoint สำหรับการรันครั้งนั้นๆ:
docker compose run --rm --entrypoint sh webนี่เป็นสาเหตุที่พบบ่อยที่สุดที่ทำให้คำสั่งซึ่งทำงานได้ปกติภายใต้ exec มีพฤติกรรมต่างออกไปเมื่อใช้ run และ ความแตกต่างระหว่าง command และ entrypoint จะอธิบายว่าคุณกำลังแทนที่ส่วนใดของการตั้งค่า image ในแต่ละครั้ง
การเลือกใช้ exec หรือ run
- exec จำเป็นต้องมี container ที่กำลังทำงานอยู่ ส่วน run ไม่จำเป็น และ run อาจเริ่มการทำงานของ dependency ต่างๆ ได้
- exec จะเห็นรายการ process ที่กำลังทำงานจริงและไฟล์ในสถานะปัจจุบัน รวมถึงสิ่งที่แอปพลิเคชันเขียนลงไปตั้งแต่เริ่มทำงาน ส่วน run จะได้สำเนาที่สะอาดจาก image ดังนั้นข้อมูลเหล่านั้นจะไม่มีอยู่
- exec จะข้าม entrypoint ไป ส่วน run จะเรียกใช้งาน entrypoint นั้น
- run จะทิ้ง container ไว้เบื้องหลังเว้นแต่คุณจะระบุ
--rm
ให้ใช้ exec เพื่อตรวจสอบสิ่งที่เกิดขึ้นจริง ให้ใช้ run --rm สำหรับสำเนาชั่วคราวในสภาพแวดล้อมเดียวกัน สำหรับคำสั่ง migration แบบครั้งเดียว หรือในกรณีที่บริการจริงไม่สามารถทำงานค้างไว้นานพอที่จะใช้ exec เข้าไปได้
แฟล็กที่มีประโยชน์สำหรับการสั่ง exec: ผู้ใช้, ไดเรกทอรีทำงาน และจำนวนรีพลิกา
อิมเมจส่วนใหญ่จะเปลี่ยนสิทธิ์ไปเป็นผู้ใช้ที่ไม่ใช่ root ดังนั้นการติดตั้งเครื่องมือวินิจฉัยภายในเชลล์ที่คุณ exec เข้าไปจึงทำไม่ได้:
E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)-u root ช่วยให้คุณเข้าถึง root shell ในคอนเทนเนอร์เดียวกันได้:
docker compose exec -u root web sh-w /srv/app กำหนดไดเรกทอรีทำงานสำหรับคำสั่งนั้นๆ เท่านั้น -e KEY=value เพิ่มตัวแปรสภาพแวดล้อมให้กับเซสชันของคุณโดยไม่ส่งผลต่อตัวบริการ เมื่อบริการรันมากกว่าหนึ่งรีพลิกา --index 2 จะเป็นตัวกำหนดว่าคุณจะเข้าไปที่คอนเทนเนอร์ใด หากคุณกำลังตรวจสอบความเป็นเจ้าของไฟล์บนไดเรกทอรีที่ mount ไว้ PUID และ PGID ในอิมเมจคอนเทนเนอร์ จะอธิบายว่าเหตุใด ID ที่เป็นตัวเลข ไม่ใช่ชื่อผู้ใช้ จึงเป็นตัวตัดสินว่าใครสามารถเขียนไฟล์ในตำแหน่งนั้นได้
เข้าใช้งาน psql หรือ mysql shell ภายในคอนเทนเนอร์ฐานข้อมูล
เนื่องจากไคลเอนต์ถูกรวมอยู่ในอิมเมจฐานข้อมูลอยู่แล้ว คุณจึงไม่จำเป็นต้องติดตั้งไคลเอนต์บนโฮสต์และไม่จำเป็นต้องเปิดพอร์ต (publish port):
docker compose exec db psql -U postgres -d app
docker compose exec db mariadb -u root -pอิมเมจ Postgres จะมี psql มาให้ อิมเมจ MySQL จะมี mysql และอิมเมจ MariaDB จะมี mariadb การเชื่อมต่อจะเกิดขึ้นจากภายในคอนเทนเนอร์ ดังนั้นวิธีนี้จึงใช้งานได้แม้ในไฟล์ compose จะไม่ได้เปิดพอร์ตฐานข้อมูลไว้เลย ซึ่งเป็นวิธีที่ปลอดภัยกว่า เพราะไม่มีสิ่งใดบนอินเทอร์เน็ตสามารถเข้าถึงพอร์ตที่คุณไม่ได้เปิดไว้ได้
มีกับดักหนึ่งอย่างที่ทำให้เสียเวลาไปทั้งบ่าย คือเชลล์จะขยายค่าตัวแปร (expand variables) บนโฮสต์ก่อนที่ Docker จะเห็นคำสั่ง ดังนั้น -U "$POSTGRES_USER" จะส่งค่าเป็นสตริงว่างหากตัวแปรนั้นมีอยู่เฉพาะภายในคอนเทนเนอร์เท่านั้น การใช้เครื่องหมายคำพูดเดี่ยวและเรียกใช้เชลล์ภายในคอนเทนเนอร์จะช่วยให้การขยายค่าตัวแปรเกิดขึ้นในตำแหน่งที่ถูกต้อง:
docker compose exec db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"'อย่าใช้ docker compose run --rm db โดยไม่มีคำสั่งตามหลัง เพราะจะเป็นการเริ่มเซิร์ฟเวอร์ Postgres ตัวที่สองโดยใช้ data volume เดียวกัน ซึ่งเซิร์ฟเวอร์จะปฏิเสธการทำงาน:
FATAL: lock file "postmaster.pid" already existsไฟล์ล็อก (lock file) กำลังทำหน้าที่ของมัน เพราะหากเซิร์ฟเวอร์สองตัวเขียนข้อมูลลงในไดเรกทอรีเดียวกันจะทำให้ข้อมูลเสียหาย ในขณะที่ฐานข้อมูลกำลังทำงานอยู่ ให้ใช้คำสั่ง exec เข้าไปในคอนเทนเนอร์ที่รันอยู่ ส่วนเรื่องที่ว่าฐานข้อมูลควรอยู่ใน Compose หรือไม่นั้นเป็นการตัดสินใจแยกต่างหาก ซึ่งบทความเรื่อง การรันฐานข้อมูลใน Docker หรือบนโฮสต์ ได้อธิบายข้อดีข้อเสียไว้แล้ว
บริการที่ต้องการคอนโซลขณะเริ่มต้น: stdin_open และ tty
คำสั่ง exec และ run ครอบคลุมถึงเชลล์ที่คุณเปิดใช้งานด้วยตนเอง บริการที่มีกระบวนการหลักเป็นแบบโต้ตอบ (interactive) จำเป็นต้องใช้คีย์สองตัวในไฟล์ compose:
services:
console:
image: python:3.12-slim
command: python
stdin_open: true
tty: truestdin_open: true คือ docker run -i และ tty: true คือ docker run -t หากไม่มีคีย์เหล่านี้ คอนเทนเนอร์จะเริ่มทำงานและหยุดลงทันทีด้วยรหัส 0 และ docker compose ps -a จะแสดง Exited (0) ซึ่งไม่ใช่การขัดข้อง (crash) แต่อย่างใด python ที่ไม่มีเทอร์มินัลบน stdin จะอ่านพบจุดสิ้นสุดของไฟล์ (end of file) ทันทีและจบการทำงานตามปกติ ซึ่งเป็นพฤติกรรมที่ถูกต้องสำหรับโปรแกรมที่ไม่มีผู้ใช้งานคอยพิมพ์คำสั่ง
เมื่อตั้งค่าคีย์ทั้งสองแล้ว ให้เชื่อมต่อเข้ากับกระบวนการที่กำลังทำงานอยู่ด้วยคำสั่ง:
docker attach $(docker compose ps -q console)หากต้องการแยกตัวออกมา (detach) ให้กด Ctrl-P ตามด้วย Ctrl-Q ซึ่งจะทำให้กระบวนการยังคงทำงานต่อไป ลำดับการกดนี้จะใช้ได้ก็ต่อเมื่อคอนเทนเนอร์มีการเปิดทั้ง TTY และ stdin ไว้เท่านั้น หากกด Ctrl-C จะเป็นการส่งสัญญาณขัดจังหวะ (interrupt) ไปยัง PID 1 และหยุดการทำงานของบริการนั้น
ให้ปิดคีย์ทั้งสองไว้สำหรับบริการทั่วไป เว็บเซิร์ฟเวอร์ไม่เคยอ่านค่าจาก stdin และ tty: true จะทำให้โปรแกรมจำนวนมากเปลี่ยนไปใช้การแสดงผลแบบมีสีและทำ line buffering เพราะโปรแกรมเหล่านั้นเข้าใจว่ามีมนุษย์กำลังเฝ้าดูอยู่ ซึ่งจะทำให้ docker compose logs เต็มไปด้วยรหัสควบคุม (escape codes)
เหตุผลที่การสั่ง exec ผ่านสคริปต์ล้มเหลวใน cron และ CI: แฟล็ก -T
คำสั่ง exec ที่ทำงานได้ปกติในเทอร์มินัลของคุณกลับล้มเหลวเมื่อรันภายในงาน cron หรือระบบ continuous integration (CI):
the input device is not a TTYโดยปกติแล้ว Compose จะร้องขอ pseudo terminal เสมอ แต่ cron ไม่ได้จัดเตรียมเทอร์มินัลให้งานดังกล่าว คำสั่งจึงล้มเหลวก่อนที่จะเริ่มทำงานจริง -T จะช่วยปิดการร้องขอนี้:
0 3 * * * docker compose -f /srv/app/compose.yaml exec -T db pg_dump -U postgres -Fc app > /srv/backups/app.dump-T มีความสำคัญด้วยเหตุผลประการที่สอง เนื่องจาก TTY จะเขียนข้อมูลใน byte stream ใหม่ระหว่างส่งออก ดังนั้นไฟล์ dump ที่ถูกบีบอัดซึ่งผ่าน TTY จะได้รับความเสียหาย ข้อมูลใดก็ตามที่มีการ redirect หรือส่งผ่าน pipe จำเป็นต้องใช้ -T
รายละเอียดเพิ่มเติมอีกสองประการสำหรับ cron: ให้ส่ง -f พร้อมระบุ absolute path เนื่องจาก cron รันงานจาก home directory ซึ่งไม่มีไฟล์ compose อยู่ ส่งผลให้ Compose หยุดทำงานพร้อมกับแจ้ง no configuration file provided: not found นอกจากนี้ exec จะส่งคืน exit code ของคำสั่งที่รัน ดังนั้นหาก pg_dump ล้มเหลว สคริปต์ของคุณจะล้มเหลวภายใต้ set -e แทนที่จะเขียนไฟล์สำรองที่ว่างเปล่าแล้วรายงานว่าสำเร็จ คำสั่งที่ใช้ในชีวิตประจำวันอื่นๆ ถูกรวบรวมไว้ใน ตารางสรุปคำสั่ง Compose ซึ่งควรเก็บไว้ใช้งานคู่กับสคริปต์เหล่านั้น
เหตุใดการเปลี่ยนแปลงที่คุณทำภายในคอนเทนเนอร์จึงหายไป
คุณติดตั้งเครื่องมือผ่าน exec แก้ไขไฟล์คอนฟิก และแก้ไขปัญหาได้สำเร็จ แต่หนึ่งสัปดาห์ต่อมาการแก้ไขนั้นกลับหายไป นี่คือพฤติกรรมปกติของ writable layer ในคอนเทนเนอร์ docker compose up -d หลังจากมีการเปลี่ยนแปลงใดๆ กับ image tag หรือ service definition จะเป็นการทำลายคอนเทนเนอร์เก่าและสร้างคอนเทนเนอร์ใหม่จาก image นั้น ทำให้การแก้ไขด้วยมือทั้งหมดหายไปพร้อมกับคอนเทนเนอร์เก่า
docker compose restart มีการทำงานที่ต่างออกไป โดยจะหยุดและเริ่มคอนเทนเนอร์เดิมใหม่ ทำให้การแก้ไขด้วยมือยังคงอยู่ นั่นคือเหตุผลว่าทำไมการแก้ไขด้วยมืออาจดูเหมือนใช้งานได้นานหลายสัปดาห์แล้วจึงหายไปในระหว่างการอัปเดตที่ไม่เกี่ยวข้อง ทั้งนี้ Named volumes และ bind mounts จะคงอยู่ผ่านการดำเนินการทั้งสองรูปแบบเนื่องจากข้อมูลถูกจัดเก็บไว้นอกคอนเทนเนอร์ โดย bind mounts และ named volumes จะอธิบายถึงวิธีการเลือกใช้งานสำหรับข้อมูลที่คุณต้องการเก็บรักษาไว้
ดังนั้น ให้ถือว่าการใช้ exec shell เป็นเพียงพื้นที่สำหรับการอ่านและทดสอบเท่านั้น เมื่อคุณทราบวิธีแก้ไขแล้ว ให้บันทึกการแก้ไขนั้นในที่ที่ข้อมูลจะคงอยู่ถาวร เช่น การเพิ่มแพ็กเกจลงใน Dockerfile หรือการเพิ่มการตั้งค่าลงใน compose file จากนั้นใช้ docker compose up -d เพื่อนำการเปลี่ยนแปลงไปใช้ และตรวจสอบอีกครั้งด้วย exec เพื่อยืนยันว่าคอนเทนเนอร์ใหม่มีการแก้ไขนั้นจริง
FAQ
docker compose exec กับ docker compose run ต่างกันอย่างไร
exec ใช้รันคำสั่งภายในคอนเทนเนอร์ที่กำลังทำงานอยู่ โดยรันควบคู่ไปกับกระบวนการหลักและข้าม entrypoint ของอิมเมจ ส่วน run จะสร้างคอนเทนเนอร์ใหม่จากนิยามของ service เดียวกัน โดยใช้อิมเมจ, environment, volumes และ networks ชุดเดียวกัน พร้อมทั้งส่งคำสั่งของคุณผ่าน entrypoint และเริ่มบริการ depends_on ที่จำเป็นก่อน นอกจากนี้ run จะไม่เปิดพอร์ตของ service เว้นแต่คุณจะเพิ่ม --service-ports ให้ใช้ exec เพื่อตรวจสอบ service ที่กำลังทำงานอยู่ และใช้ run --rm ในกรณีที่ service หยุดทำงานหรือเมื่อคุณไม่ต้องการรบกวนการทำงานของ service นั้น
ทำไม docker compose exec ถึงแจ้งว่า service ไม่ได้ทำงานอยู่
exec จะเชื่อมต่อกับคอนเทนเนอร์ที่มีอยู่แล้วและไม่สามารถสร้างคอนเทนเนอร์ใหม่ได้ ดังนั้นหาก service หยุดทำงานหรือล้มเหลวจะได้รับ service "web" is not running ให้ตรวจสอบด้วย docker compose ps -a ซึ่งจะแสดงรายการคอนเทนเนอร์ที่หยุดทำงานพร้อมสถานะ เช่น Exited (1) และอ่าน docker compose logs web เพื่อดูสาเหตุที่หยุดทำงาน หากต้องการเข้าใช้งาน shell ให้รัน docker compose run --rm --entrypoint sh web แทน ซึ่งจะเป็นการสร้างคอนเทนเนอร์ใหม่จากนิยามของ service โดยไม่รันคำสั่ง start ที่มีปัญหา
จะเปิด shell ได้อย่างไรหากอิมเมจไม่มี bash
หาก docker compose exec web bash ล้มเหลวด้วยข้อความ exec: "bash": executable file not found in $PATH แสดงว่าไม่มี bash ในอิมเมจ ซึ่งเป็นเรื่องปกติสำหรับอิมเมจที่สร้างบน Alpine ให้ใช้ docker compose exec web sh แทน เนื่องจาก BusyBox มี /bin/sh ให้ใช้งาน สำหรับอิมเมจแบบ Distroless หรือ scratch จะไม่มี shell ใดๆ เลย ดังนั้นคำสั่ง exec จะใช้งานไม่ได้ ให้เปลี่ยนไปใช้แท็ก :debug ของอิมเมจนั้นหากผู้ผลิตมีให้ หรือเริ่มคอนเทนเนอร์สำหรับ debug ใน namespace ของเป้าหมายด้วย docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshoot โดยที่ $CID ได้มาจาก docker compose ps -q web
ทำไมคำสั่ง exec ถึงล้มเหลวด้วยข้อความ "the input device is not a TTY" ใน cron
docker compose exec จะร้องขอ pseudo terminal โดยค่าเริ่มต้น แต่ cron ไม่มี terminal ให้ คำสั่งจึงล้มเหลวก่อนที่จะเริ่มทำงาน ให้เพิ่ม -T เพื่อปิดการใช้งาน: docker compose exec -T db pg_dump -U postgres app และควรใช้ -T สำหรับการส่งออกข้อมูลแบบ redirect หรือ pipe ด้วย เนื่องจาก TTY จะปรับเปลี่ยน byte stream และทำให้ข้อมูล binary เสียหาย ใน cron คุณต้องระบุ -f พร้อม path แบบเต็มไปยังไฟล์ compose ของคุณ มิฉะนั้น Compose จะหยุดทำงานด้วย no configuration file provided: not found
การเปลี่ยนแปลงที่ทำภายในคอนเทนเนอร์ด้วย exec จะคงอยู่หลังการ restart หรือไม่
การเปลี่ยนแปลงจะคงอยู่เมื่อใช้ docker compose restart ซึ่งเป็นการใช้คอนเทนเนอร์เดิมซ้ำ แต่จะสูญหายไปเมื่อใช้ docker compose up -d หลังจากมีการเปลี่ยนแปลงอิมเมจหรือการตั้งค่า เพราะจะเป็นการสร้างคอนเทนเนอร์ใหม่จากอิมเมจและทิ้ง writable layer เดิมไป ข้อมูลที่เขียนลงใน named volumes หรือ bind mounts จะคงอยู่ทั้งสองกรณีเนื่องจากข้อมูลเหล่านั้นอยู่นอกคอนเทนเนอร์ ให้ใช้ exec สำหรับการแก้ไขปัญหาชั่วคราว จากนั้นจึงนำการตั้งค่าถาวรไปใส่ไว้ใน Dockerfile หรือไฟล์ compose แทน