วิธีใช้ Docker Compose หลายไฟล์และการรวมไฟล์ค่าคอนฟิก
เรียนรู้วิธีการรวมไฟล์ Docker Compose หลายชุดอย่างถูกต้อง เข้าใจลำดับการอ่านไฟล์ compose.override.yaml ปัญหาการทับซ้อนของพอร์ต และการใช้คำสั่ง include สำหรับแยกสภาพแวดล้อม dev และ prod
สิ่งที่ Compose ทำเมื่อมีไฟล์มากกว่าหนึ่งไฟล์
Docker Compose สามารถสร้างโปรเจกต์เดียวจากหลายไฟล์ได้ โดยจะอ่านไฟล์ตามลำดับที่ได้รับและรวมเข้าเป็นโมเดลเดียว ดังนั้นไฟล์ที่อ่านทีหลังจะเขียนทับค่าใดก็ตามที่ซ้ำกัน มีกลไกสองอย่างที่ทำหน้าที่นี้จากบรรทัดคำสั่ง ได้แก่ ไฟล์ override ที่ Compose โหลดเองโดยอัตโนมัติ และแฟล็ก -f ที่คุณระบุด้วยตนเอง ส่วนกลไกที่สามจะอยู่ในตัวไฟล์เองคือองค์ประกอบ include ซึ่งทำงานแตกต่างจากสองวิธีแรก
การรวมไฟล์ไม่ใช่การเขียนทับแบบธรรมดา การแมปข้อมูลจะรวมกันทีละคีย์ ลำดับข้อมูลจะถูกต่อท้าย และฟิลด์จำนวนหนึ่งจะถูกแทนที่ทั้งหมด ความแตกต่างนี้เองที่เป็นสาเหตุของปัญหาที่คาดไม่ถึง และรายการ ports คือจุดที่ทำให้ผู้ใช้ส่วนใหญ่เกิดความสับสน
เนื้อหาทั้งหมดด้านล่างนี้ตั้งอยู่บนสมมติฐานว่าคุณใช้ Compose v2 หรือใหม่กว่า ซึ่งเป็นปลั๊กอิน docker compose ไม่ใช่สคริปต์ docker-compose แบบเก่า เวอร์ชันปัจจุบันคือ v5 และปลั๊กอินยังคงทำงานในลักษณะเดียวกัน ให้รันคำสั่ง docker compose version เพื่อตรวจสอบ หากคุณยังไม่ได้เขียนไฟล์ Compose ให้เริ่มต้นที่ คู่มือพื้นฐาน Docker Compose แล้วค่อยกลับมาอ่านต่อ
ไฟล์ override ที่ Compose โหลดโดยอัตโนมัติ
เมื่อรัน docker compose up โดยไม่ระบุแฟล็ก -f ตัว Compose จะค้นหาไฟล์ compose.yaml หรือ docker-compose.yaml ในไดเรกทอรีปัจจุบันและไดเรกทอรีแม่ หากมีไฟล์ override วางอยู่ข้างไฟล์หลัก Compose จะโหลดไฟล์นั้นตามมาโดยอัตโนมัติ
ls compose.yaml compose.override.yaml
docker compose up -dหากมีไฟล์ทั้งสองอยู่ จะให้ผลลัพธ์เหมือนกับการระบุไฟล์ด้วยตนเองผ่านคำสั่ง
docker compose -f compose.yaml -f compose.override.yaml up -dชื่อไฟล์ที่ Compose รองรับคือ compose.override.yaml, compose.override.yml รวมถึงชื่อเดิมอย่าง docker-compose.override.yml และ docker-compose.override.yaml ส่วนชื่ออื่น เช่น compose.dev.yaml จะถูกโหลดก็ต่อเมื่อคุณระบุชื่อไฟล์ผ่าน -f เท่านั้น
ทันทีที่คุณระบุแฟล็ก -f การโหลดไฟล์โดยอัตโนมัติจะหยุดลง docker compose -f compose.yaml up จะอ่านเฉพาะไฟล์ที่ระบุเท่านั้นและเพิกเฉยต่อไฟล์ override ซึ่งเป็นคุณสมบัติที่รูปแบบการใช้งาน dev และ prod ในคู่มือนี้ใช้เป็นพื้นฐาน
เรื่องนี้มีผลได้ทั้งในทางที่ดีและไม่ดีบนเซิร์ฟเวอร์ ไฟล์ override ที่ค้างอยู่ในไดเรกทอรี deploy จะถูกโหลดโดยคำสั่ง docker compose แบบไม่ระบุไฟล์ที่เรียกใช้จากไดเรกทอรีนั้นทุกคำสั่ง รวมถึงคำสั่งที่ cron job เรียกใช้ด้วย นี่เป็นสาเหตุที่ทำให้ production stack ลงท้ายด้วยการ bind-mount ไดเรกทอรี source ที่ไม่มีใครตั้งใจจะนำไปใช้งานจริง
ให้เรียกใช้ docker compose config หลัง deploy ทุกครั้ง แล้วอ่านผลลัพธ์ที่ได้ หาก deploy นั้นทำงานโดยไม่มีผู้ดูแล การตรวจสอบจะมีประโยชน์ก็ต่อเมื่อมีสิ่งแจ้งให้ทราบว่าการทำงานผิดพลาด ซึ่งเป็นหน้าที่ของ push channel เช่น เซิร์ฟเวอร์ ntfy ที่โฮสต์เอง ที่ cron job หรือ systemd OnFailure unit สามารถส่งข้อมูลไปได้
การจัดลำดับด้วย -f และการระบุตำแหน่งของ relative path
Compose จะสร้างการกำหนดค่าตามลำดับไฟล์ที่คุณระบุ โดยไฟล์ที่ตามมาจะเขียนทับและเพิ่มข้อมูลลงในไฟล์ก่อนหน้า การประมวลผลจะเริ่มจากซ้ายไปขวา และไฟล์สุดท้ายจะมีผลเหนือกว่าไฟล์อื่น
docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -dทุกคำสั่งในโปรเจกต์นั้นจำเป็นต้องใช้รายการไฟล์ชุดเดียวกัน หากคุณรัน up ด้วยไฟล์สองไฟล์ และรัน logs ด้วยไฟล์เพียงไฟล์เดียว คุณกำลังสื่อสารกับโมเดลที่รวมกันคนละชุด ซึ่งเป็นวิธีที่รวดเร็วในการทำให้เกิดข้อผิดพลาดว่าบริการที่ Compose แจ้งว่าไม่มีอยู่จริง ความเสี่ยงจะสูงขึ้นเมื่อใช้งานกับ stack ที่มีการอัปเกรดผ่านคำสั่งแบบครั้งเดียว (one-off commands) เช่น ขั้นตอนการทำ database migration ใน ระบบ support desk แบบ self-hosted อย่าง Chatwoot ซึ่งหากมีการใช้ docker compose run ด้วยรายการไฟล์ที่ผิดพลาด ระบบจะไปจัดการกับโมเดลอื่นโดยเงียบๆ แทนที่จะเป็นโมเดลที่บริการของคุณกำลังใช้งานอยู่ ให้ตั้งค่ารายการไฟล์ไว้เพียงครั้งเดียวด้วยตัวแปรสภาพแวดล้อม COMPOSE_FILE แทน
export COMPOSE_FILE=compose.yaml:compose.prod.yaml
docker compose config
docker compose up -dตัวคั่นคือ : บน Linux และ COMPOSE_PATH_SEPARATOR จะใช้สำหรับเปลี่ยนตัวคั่นดังกล่าว คุณสามารถกำหนด COMPOSE_FILE ไว้ในไฟล์ .env ของโปรเจกต์ได้ ซึ่งจะทำให้การตั้งค่านี้เป็นส่วนหนึ่งของโค้ดที่ checkout มา แทนที่จะอยู่ในประวัติของ shell อย่างไรก็ตาม สิ่งที่กำหนดไว้อย่างชัดเจนบนบรรทัดคำสั่งจะมีลำดับความสำคัญสูงกว่าตัวแปรสภาพแวดล้อม
ต่อมาคือกฎที่ส่งผลต่อ bind mounts เมื่อคุณใช้ไฟล์หลายไฟล์ร่วมกับ -f เส้นทางแบบ relative path ทั้งหมดในไฟล์เหล่านั้นจะถูกอ้างอิงจากไดเรกทอรีของไฟล์ แรก ไม่ใช่จากไฟล์ที่ระบุเส้นทางนั้น หากคุณเขียน ./data:/var/lib/postgresql/data ไว้ภายใน deploy/prod/compose.prod.yaml ตัว Compose จะยังคงมองหา ./data ที่ตำแหน่งข้างไฟล์ฐาน (base file) จากนั้น Docker จะสร้างไดเรกทอรีเปล่าขึ้นมาในเส้นทางที่ผิดนั้น และคอนเทนเนอร์จะเริ่มทำงานโดยไม่มีข้อมูลอยู่ภายใน ซึ่งอาจดูเหมือนข้อมูลสูญหายแต่จริงๆ แล้วไม่ใช่ ให้ส่งค่า --project-directory เพื่อกำหนด base path ด้วยตนเอง หรือใช้ include ซึ่งจะอ้างอิงแต่ละไฟล์จากไดเรกทอรีของตัวมันเอง
ชื่อของโปรเจกต์จะถูกกำหนดจากไดเรกทอรีฐานเดียวกันนี้ ดังนั้นการเปลี่ยนไฟล์แรกในรายการอาจทำให้ชื่อโปรเจกต์เปลี่ยนไป เมื่อโปรเจกต์ถูกเปลี่ยนชื่อ จะส่งผลให้ชื่อคอนเทนเนอร์และชื่อ volume เปลี่ยนตามไปด้วย และ volume เดิมจะยังคงค้างอยู่ในดิสก์ภายใต้ชื่อเก่า ให้ล็อกชื่อโปรเจกต์ไว้ด้วยการกำหนด name: ไว้ที่ระดับบนสุดในไฟล์ฐานแทน
name: myappฟิลด์ใดบ้างที่รวมเข้าด้วยกัน และฟิลด์ใดบ้างที่ถูกแทนที่
Compose จะรวมค่าโดยพิจารณาจากประเภทของข้อมูล ไม่ใช่ชื่อของฟิลด์
- ฟิลด์ที่มีค่าเดียวจะถูกแทนที่
image,command,entrypointและmem_limitจะใช้ค่าที่ระบุในภายหลังแทนที่ค่าเดิมทั้งหมด คุณไม่สามารถเพิ่มอาร์กิวเมนต์ต่อท้ายcommandได้ เนื่องจากค่าที่กำหนดใหม่จะเขียนทับทั้งบรรทัด - การแมป (Mappings) จะรวมกันแบบคีย์ต่อคีย์
environment,labels,volumesและdevicesจะเก็บทุกคีย์จากทั้งสองไฟล์ไว้ โดยไฟล์ที่ระบุในภายหลังจะมีความสำคัญเหนือกว่าหากมีคีย์ที่ซ้ำกัน สำหรับenvironmentและlabelsคีย์คือชื่อตัวแปรหรือชื่อเลเบล สำหรับvolumesและdevicesคีย์คือพาธภายในคอนเทนเนอร์ - ลำดับ (Sequences) จะถูกนำมาต่อท้ายกัน
dns,dns_search,expose,tmpfsและexternal_linksจะถูกนำมาเชื่อมต่อกัน ไฟล์ฐานที่มีexpose: ["3000"]เมื่อรวมกับไฟล์ที่กำหนดค่าทับ (override) ที่มี["4000", "5000"]จะได้ผลลัพธ์เป็น["3000", "4000", "5000"]
มีลำดับอยู่สี่รายการที่มีคีย์ระบุตัวตน (identity key) ดังนั้นรายการที่ตรงกันในคีย์ดังกล่าวจะถูกรวมเข้าด้วยกันแทนที่จะต่อท้าย volumes, secrets และ configs จะจับคู่กันที่ target ส่วน ports จะจับคู่กันที่การรวมกันของ ip, target, published และ protocol
โปรดอ่านกฎ ports นั้นซ้ำสองครั้ง เพราะนั่นคือจุดที่มักเกิดข้อผิดพลาด รายการพอร์ตสองรายการจะเป็นรายการเดียวกันก็ต่อเมื่อส่วนประกอบทั้งสี่ส่วนนั้นตรงกันทั้งหมด หากคุณเปลี่ยนส่วนใดส่วนหนึ่งไป Compose จะมองว่าเป็นพอร์ตที่สองที่ไม่เกี่ยวข้องกัน และจะเก็บไว้ทั้งสองรายการ
เหตุใดพอร์ตของคุณจึงยังคงถูกเผยแพร่หลังจากทำการ override
ไฟล์ฐานที่เผยแพร่บริการบนทุกอินเทอร์เฟซ:
services:
web:
image: nginx:1.27
ports:
- "8080:80"ไฟล์ override ที่เขียนขึ้นเพื่อผูกไว้กับ localhost เท่านั้น เนื่องจากจะมี reverse proxy ทำงานอยู่ด้านหน้า:
services:
web:
ports:
- "127.0.0.1:8080:80"ตรวจสอบผลลัพธ์ก่อนที่จะสรุปว่าการตั้งค่าทำงานได้ผล:
docker compose -f compose.yaml -f compose.prod.yaml configรายการทั้งสองปรากฏอยู่ในผลลัพธ์ ส่วนของ ip นั้นแตกต่างกัน โดยเป็น 0.0.0.0 เทียบกับ 127.0.0.1 ดังนั้นในมุมมองของการรวมไฟล์ (merge) ทั้งสองจึงเป็นพอร์ตที่ต่างกัน และการผูกพอร์ตแบบสาธารณะที่คุณพยายามจะลบออกยังคงอยู่ในโมเดล สิ่งนี้มีความสำคัญบน Docker มากกว่าที่อื่น เนื่องจากพอร์ตที่ถูกเผยแพร่จะถูกเขียนลงใน iptables ก่อนกฎ firewall ของคุณ กลไกนี้ถูกอธิบายไว้ใน เหตุใดพอร์ต Docker ที่เผยแพร่จึงข้าม ufw
มีวิธีแก้ไขสองวิธี วิธีที่ชัดเจนคือการใช้แท็ก !override ซึ่งจะเข้ามาแทนที่แอตทริบิวต์ทั้งหมดและข้ามกฎการรวมไฟล์ไป:
services:
web:
ports: !override
- "127.0.0.1:8080:80"!override จำเป็นต้องใช้ Compose v2.24.4 หรือใหม่กว่า ส่วนวิธีแก้ไขที่ยืดหยุ่นกว่านั้นไม่จำเป็นต้องใช้แท็กใดๆ เลย: ให้เก็บ ports ไว้นอกไฟล์ฐานโดยสมบูรณ์ และประกาศไว้เฉพาะในไฟล์ที่ระบุสภาพแวดล้อม (environment-specific) เท่านั้น เมื่อไม่มีอะไรให้รวม ก็จะไม่มีอะไรหลุดรอดออกไป นี่คือรูปแบบที่ใช้ในตัวอย่างการทำงานด้านล่างนี้
การลบค่าออกจากชุดไฟล์ฐาน
!reset จะลบแอตทริบิวต์ออก โดยคืนค่ากลับไปเป็นค่าเริ่มต้นหรือค่าว่าง คำสั่งนี้ต้องการค่าอินพุตแม้ว่าจะไม่ได้นำไปใช้งานจริง ดังนั้นให้ระบุค่าที่ถูกต้องแต่เป็นค่าว่าง
services:
web:
ports: !reset []
environment:
DEBUG: !reset null!reset สามารถใช้งานได้บน Compose v2.18.0 หรือใหม่กว่า ซึ่งเป็นเวอร์ชันที่ออกมาก่อน !override ให้เลือกใช้คำสั่งนี้ในกรณีที่คุณไม่สามารถแก้ไขไฟล์ฐานได้ เช่น กรณีที่เป็นไฟล์ส่วนขยายจากผู้ให้บริการที่คุณดึงมาใช้งาน สแต็กที่เผยแพร่จากต้นทางเป็นตัวอย่างที่ชัดเจนของกรณีนี้ ไฟล์ Compose ที่อยู่เบื้องหลัง AFFiNE workspace แบบ self-hosted ประกาศคอนเทนเนอร์ 4 รายการที่คุณไม่ได้เป็นผู้เขียนขึ้นเอง และ !reset ช่วยให้คุณสามารถล้างแอตทริบิวต์รายการหนึ่งบนคอนเทนเนอร์รายการใดรายการหนึ่งได้ โดยไม่จำเป็นต้องทำ forking ไฟล์นั้นและไม่ต้องรับภาระในการติดตามการเปลี่ยนแปลงไฟล์ต้นฉบับด้วยตนเอง
include สำหรับประกอบ stack จากหลายส่วน
include ดึง Compose application อื่นเข้ามาใน model ของคุณ โดยเป็นองค์ประกอบระดับบนสุด ไม่ใช่ flag
include:
- path: ../commons/compose.yamlแต่ละ path ใน include จะถูกโหลดเป็น Compose application model แยกกัน โดยมี project directory ของตนเอง ดังนั้น relative path ภายในไฟล์นั้นจะอ้างอิงจาก directory ของไฟล์นั้นเอง นี่คือความแตกต่างที่แท้จริงจาก -f และเป็นเหตุผลที่ include เหมาะสำหรับกรณีที่ fragment อยู่ใน folder อื่นหรือ repository อื่น ลักษณะนี้พบได้ทั่วไปกับ vendor stack ที่คุณไม่ได้เขียนเอง เช่น Compose file แบบหลาย service ที่อยู่เบื้องหลัง การติดตั้ง Authentik SSO แบบ self-hosted อาจอยู่ใน directory ของตนเองพร้อม relative path ของตัวเอง โดยที่ไฟล์ของคุณยังจัดการเฉพาะ service ของคุณ
รูปแบบเต็มรองรับ sub-option
include:
- path:
- ../monitoring/compose.yaml
- ../monitoring/compose.vps.yaml
project_directory: ../monitoring
env_file: ../monitoring/.envpath รับค่าเป็น list และไฟล์เหล่านั้นจะถูก merge เข้าด้วยกันตามกฎปกติก่อนนำผลลัพธ์ไปรวมใน model ของคุณ project_directory กำหนด base path ที่ใช้ resolve relative path ในไฟล์ที่ include env_file ระบุไฟล์ที่ Compose file ที่ถูก include ใช้เป็นแหล่งค่าเริ่มต้นสำหรับ interpolation ค่าเริ่มต้นคือ .env ใน project directory ของไฟล์นั้นเอง และ environment ของ local project ยังคงมี precedence สูงกว่า จึงสามารถ override ตัวแปรใดตัวแปรหนึ่งได้โดยไม่ต้องแก้ไข fragment include ต้องใช้ Compose v2.20.0 หรือใหม่กว่า ตัวเลือกแบบเดียวกันนี้เหมาะกับ add-on แบบ single-container ที่เพิ่มเข้าใน stack ซึ่งคุณใช้งานอยู่แล้ว เช่น Halcyon ซึ่งเปลี่ยนหน้าตา library ของ Jellyfin ให้เหมือนร้านเช่าวิดีโอยุค 90s: ไฟล์ของมันยังคง image tag และ env_file ของตัวเอง ดังนั้นการอัปเกรดจึงไม่ต้องแก้ไขไฟล์ที่ media stack ของคุณใช้งานอยู่
เอกสารของ Docker ระบุว่า resource name ที่ประกาศซ้ำทั้งในไฟล์ของคุณและไฟล์ที่ include จะถูกปฏิเสธว่าเป็น conflict แต่ loader ใน release ปัจจุบันไม่ได้ทำเช่นนั้น โดย ณ Compose v5.6.1 ซึ่งตรวจสอบกับ source ของ loader ที่มากับ release เมื่อ 2026-10-27 ระบบจะ merge definition ทั้งสองรายการ โดยให้ไฟล์ของคุณมี precedence เมื่อค่าขัดแย้งกัน แทนที่จะหยุดทำงานพร้อม error ดังนั้นอย่าคาดหวังว่าชื่อที่ซ้ำกันจะทำให้ระบบแจ้งข้อผิดพลาดอย่างชัดเจน หากต้องการเปลี่ยนค่าที่ไฟล์ที่ include ประกาศไว้ ให้ใส่การเปลี่ยนแปลงนั้นใน compose.override.yaml: override จะถูกนำไปใช้กับ model ที่ประกอบเสร็จแล้ว ทำให้การเปลี่ยนแปลงมีความชัดเจนและไม่ขึ้นอยู่กับว่า release แต่ละรุ่นจะจัดการกับความขัดแย้งอย่างไร แนวทางนี้มีประโยชน์มากที่สุดกับ stack ที่ upstream file ถูกเขียนทับใหม่ทุก release เช่น photo server แบบหลาย container ที่นำมาเปรียบเทียบใน PhotoPrism กับ Immich ซึ่งการ bind กับ localhost หรือ volume เพิ่มเติมควรอยู่ใน override แทนที่จะอยู่ในไฟล์ที่การอัปเกรดครั้งถัดไปจะเขียนทับ
สรุปสั้น ๆ: include ใช้ประกอบ application แยกกันหลายรายการ ส่วน -f ใช้ซ้อน configuration ลงบน application เดียว
การแยกสภาพแวดล้อม dev และ prod บน VPS เดียวกัน
นี่คือรูปแบบการตั้งค่าทั้งหมดในไฟล์ 3 ไฟล์ ไฟล์ฐาน (base file) จะประกาศสิ่งที่ใช้ร่วมกันในทุกสภาพแวดล้อม และไม่มีการเปิดพอร์ตใดๆ ออกไป
name: myapp
services:
app:
image: ghcr.io/example/app:1.4.2
environment:
DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
LOG_LEVEL: info
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_DB: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
db_data:เงื่อนไข depends_on คือสิ่งที่ทำให้แอปพลิเคชันรอจนกว่าฐานข้อมูลจะพร้อมตอบสนอง แทนที่จะรอเพียงแค่คอนเทนเนอร์เริ่มทำงาน อธิบายไว้ใน healthchecks และเงื่อนไข depends_on ส่วน POSTGRES_PASSWORD จะถูกแทนที่ค่าจากไฟล์ .env ของโปรเจกต์ ซึ่งไม่ควรนำขึ้น git ดูรายละเอียดเกี่ยวกับตัวเลือกที่ปลอดภัยกว่าได้ที่ ไฟล์ env และ Compose secrets
ถัดมาคือ compose.override.yaml ซึ่ง Compose จะโหลดไฟล์นี้โดยอัตโนมัติ นี่คือไฟล์สำหรับนักพัฒนา
services:
app:
build: .
command: npm run dev
environment:
LOG_LEVEL: debug
ports:
- "3000:3000"
volumes:
- ./src:/app/src
db:
ports:
- "127.0.0.1:5432:5432"บนแล็ปท็อป การใช้คำสั่ง docker compose up เปล่าๆ จะเป็นการรวมไฟล์ทั้งสองเข้าด้วยกัน command จะแทนที่ค่าเริ่มต้นของ image เพราะเป็นค่าเดี่ยว LOG_LEVEL จะแทนที่ info เนื่องจาก environment ใช้วิธีรวมค่าตามคีย์ การทำ bind mount และการเปิดพอร์ตทั้งสองพอร์ตเป็นการเพิ่มค่าเข้าไปใหม่ทั้งหมด และพอร์ตของฐานข้อมูลจะถูกผูกไว้กับ localhost เพื่อไม่ให้แล็ปท็อปที่อยู่ในเครือข่ายเดียวกันเข้าถึง PostgreSQL ได้
สุดท้ายคือ compose.prod.yaml ชื่อไฟล์นี้ไม่ใช่ชื่อที่ Compose ค้นหาโดยอัตโนมัติ จึงไม่มีทางถูกโหลดขึ้นมาโดยไม่ได้ตั้งใจ
services:
app:
ports:
- "127.0.0.1:8000:3000"
deploy:
resources:
limits:
memory: 512Mบน VPS คุณต้องระบุชื่อไฟล์ทั้งสองไฟล์ ซึ่งการระบุชื่อเช่นนี้เองที่เป็นตัวกำหนดว่าไฟล์ override จะไม่ถูกนำมาใช้
docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose -f compose.yaml -f compose.prod.yaml psps ควรแสดงรายการบริการทั้งสองว่ากำลังทำงานอยู่ โดยที่ db จะแสดงสถานะ (healthy) เนื่องจากคุณได้ระบุ -f ไฟล์ compose.override.yaml จึงไม่ถูกอ่าน ส่งผลให้คำสั่ง dev, การทำ source bind mount และพอร์ตสาธารณะ 3000 ไม่สามารถเข้าถึงสภาพแวดล้อม production ได้ แม้ว่าไฟล์จะวางอยู่ในไดเรกทอรีเดียวกันก็ตาม พอร์ต 8000 จะเปิดอยู่บน localhost เท่านั้น เพื่อรอรับการเชื่อมต่อจาก proxy: ดูรายละเอียดเพิ่มเติมที่ การรันแอปพลิเคชันหลายตัวหลัง Traefik เมื่อคุณเพิ่มบริการตัวที่สอง
กำหนดค่า COMPOSE_FILE=compose.yaml:compose.prod.yaml ในไฟล์ .env ของเซิร์ฟเวอร์ แล้วคำสั่งที่เหลือของคุณจะกลับมาเป็น docker compose logs -f app แบบปกติ
สแต็กที่มีบริการเดียวก็ใช้รูปแบบเดียวกันนี้ได้ เพราะ แอปติดตามการออกกำลังกายแบบ self-hosted จำเป็นต้องตอบสนองผ่าน TLS หลัง proxy ก่อนที่คุณจะลงทะเบียน passkey ชุดแรก และไฟล์ฐานที่ไม่มี ports คือสิ่งที่ป้องกันไม่ให้การผูกพอร์ตสาธารณะที่หลงเหลืออยู่ทำงานตัดหน้า proxy
ตรวจสอบโมเดลที่รวมแล้วก่อนการ deploy
docker compose config จะแสดงผลโมเดลที่รวมและแทนที่ค่าตัวแปรทั้งหมดแล้ว นี่ไม่ใช่การแสดงตัวอย่าง แต่เป็นข้อมูลนำเข้าที่แท้จริงซึ่ง Compose จะนำไปใช้งาน ดังนั้นหากผลลัพธ์ไม่ตรงกับที่คุณคาดหวัง ให้ยึดตามผลลัพธ์ที่แสดงออกมา
docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml config --no-interpolate
docker compose -f compose.yaml -f compose.prod.yaml config --services--no-interpolate จะคงค่า ${VAR} ไว้โดยไม่ขยายความ ให้ใช้คำสั่งนี้ก่อนนำผลลัพธ์ไปวางที่ใดก็ตาม เนื่องจากคำสั่ง config แบบปกติจะแสดง secret ที่ถอดรหัสแล้วทั้งหมดออกมาเป็นข้อความธรรมดา ส่วน --services จะแสดงเฉพาะชื่อบริการ ซึ่งเป็นวิธีที่รวดเร็วในการยืนยันว่า include ได้ดึงข้อมูลตามที่คุณต้องการมาแล้วหรือไม่
รูปแบบความล้มเหลวและสิ่งที่คุณจะพบ
no configuration file provided: not found. Compose ไม่พบข้อมูลให้อ่าน คุณอยู่นอกไดเรกทอรีของโปรเจกต์ หรือ COMPOSE_FILE ระบุพาธที่ไม่มีอยู่จริง Compose จะค้นหาไฟล์ฐานเริ่มต้นในไดเรกทอรีแม่ แต่จะไม่ค้นหาไฟล์ที่คุณระบุชื่อเองในตำแหน่งอื่น
WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string. การทำ Interpolation จะอ้างอิงกับไฟล์ .env ของโปรเจกต์และสภาพแวดล้อมของเชลล์ โดยไดเรกทอรีของโปรเจกต์ในที่นี้คือไดเรกทอรีของไฟล์ -f ไฟล์แรก การ Deploy จากไดเรกทอรีอื่นที่ไม่ใช่ไดเรกทอรีที่เก็บ .env จะทำให้เกิดคำเตือนนี้และส่งผลให้ฐานข้อมูลปฏิเสธทุกการเชื่อมต่อ
การแก้ไขไฟล์ override ของคุณไม่ปรากฏใน docker compose config. อาจเป็นเพราะคุณส่งแฟล็ก -f ซึ่งเป็นการปิดการโหลดไฟล์ override อัตโนมัติ หรือ Compose พบ compose.yaml ในไดเรกทอรีแม่และไฟล์ override ของคุณไม่ได้วางอยู่ในตำแหน่งเดียวกัน การรัน docker compose config โดยไม่มีอาร์กิวเมนต์อื่นจะแสดงให้คุณเห็นว่า Compose กำลังสร้างโมเดลใดอยู่จริง
Bind mount ว่างเปล่าและ Docker สร้างไดเรกทอรีที่คุณไม่ได้ต้องการขึ้นมา. พาธสัมพัทธ์ถูกตีความโดยอ้างอิงจากไดเรกทอรีของไฟล์แรก ให้แก้ไขพาธ, ส่งแฟล็ก --project-directory หรือย้ายส่วนประกอบไปไว้หลัง include
คอนเทนเนอร์กลับมาพร้อมชื่อใหม่และวอลุ่มดูเหมือนว่างเปล่า. ชื่อโปรเจกต์เปลี่ยนไปเนื่องจากชื่อโปรเจกต์อ้างอิงตามไดเรกทอรีของไฟล์แรก ให้เพิ่ม name: ไว้ที่ระดับบนสุดของไฟล์ฐานแล้วการตั้งชื่อจะไม่เปลี่ยนแปลงอีก วอลุ่มเดิมยังคงอยู่ที่นั่นภายใต้คำนำหน้าเดิม และ docker volume ls จะแสดงให้เห็น
พอร์ตที่คุณลบออกในไฟล์ override ยังคงเปิดอยู่. การรวม ports เป็นการต่อท้ายแทนที่จะเป็นการแทนที่ ให้ยืนยันด้วย docker compose config จากนั้นให้ใช้ !override หรือย้าย ports ออกจากไฟล์ฐาน
FAQ
Compose โหลดไฟล์ compose.override.yaml โดยอัตโนมัติหรือไม่?
ใช่ เมื่อคุณรันคำสั่ง docker compose โดยไม่ระบุแฟล็ก -f ตัว Compose จะค้นหาไฟล์ compose.yaml หรือ docker-compose.yaml ในไดเรกทอรีปัจจุบันและไดเรกทอรีแม่ หากมีไฟล์ override วางอยู่ข้างกัน ไฟล์นั้นจะถูกโหลดเป็นลำดับที่สอง ชื่อไฟล์ที่ระบบรองรับคือ compose.override.yaml, compose.override.yml, docker-compose.override.yml และ docker-compose.override.yaml การระบุแฟล็ก -f จะเป็นการปิดการทำงานนี้ ดังนั้น docker compose -f compose.yaml up จะอ่านเพียงไฟล์เดียวเท่านั้น
การรวมไฟล์ -f หลายไฟล์มีลำดับอย่างไร?
เรียงจากซ้ายไปขวา Compose จะสร้างการตั้งค่าตามลำดับไฟล์ที่คุณระบุ โดยแต่ละไฟล์จะเขียนทับและเพิ่มข้อมูลต่อจากไฟล์ก่อนหน้า ดังนั้นไฟล์สุดท้ายในบรรทัดจะเป็นตัวตัดสินหากเกิดความขัดแย้ง คุณต้องใช้รายการไฟล์ชุดเดิมสำหรับทุกคำสั่งในโปรเจกต์นั้น ซึ่งเป็นเหตุผลที่มีการใช้ COMPOSE_FILE=compose.yaml:compose.prod.yaml
ทำไมพอร์ตของฉันยังคงถูกเผยแพร่หลังจากที่ฉันเขียนทับไปแล้ว?
เนื่องจากรายการ ports จะถูกระบุด้วยชุดข้อมูลทั้งหมดของ ip, target, published และ protocol การเขียนทับ 127.0.0.1:8080:80 ลงบนฐานเดิมที่เป็น 8080:80 จะมีความแตกต่างในส่วนของ ip ดังนั้น Compose จึงมองว่าเป็นพอร์ตที่สองและเก็บไว้ทั้งคู่ ให้รันคำสั่ง docker compose config แล้วคุณจะเห็นรายการทั้งสองรายการ ให้ใช้ ports: !override ใน Compose v2.24.4 หรือใหม่กว่า หรือแยก ports ออกจากไฟล์ฐานเพื่อไม่ให้มีค่าเดิมมาทำการรวม
ความแตกต่างระหว่าง include กับ -f คืออะไร?
-f เป็นการวางไฟล์หลายไฟล์ซ้อนทับกันเป็นแอปพลิเคชันเดียว โดย path สัมพัทธ์ทุกรายการในทุกไฟล์จะอ้างอิงจากไดเรกทอรีของไฟล์แรก ส่วน include เป็นการดึงแอปพลิเคชัน Compose แยกต่างหากเข้ามา และ path ที่รวมเข้ามาแต่ละรายการจะคงไดเรกทอรีของโปรเจกต์นั้นไว้ ทำให้ path สัมพัทธ์อ้างอิงจากตัวมันเอง ให้ใช้ -f สำหรับการแบ่งเลเยอร์สภาพแวดล้อมใน stack ของคุณเอง และใช้ include สำหรับส่วนประกอบที่ดูแลรักษาจากที่อื่น include จำเป็นต้องใช้ Compose v2.20.0 หรือใหม่กว่า
ฉันจะลบค่าที่ไฟล์ฐานตั้งไว้ได้อย่างไร?
ให้ใช้แท็ก !reset ใน Compose v2.18.0 หรือใหม่กว่า โดยเขียน ports: !reset [] หรือ MY_VAR: !reset null ในไฟล์ที่ใช้เขียนทับ แล้วแอตทริบิวต์นั้นจะกลับไปเป็นค่าเริ่มต้นหรือเป็น null ค่าที่คุณระบุให้กับแท็กนั้นจำเป็นต้องมีแต่จะถูกละเว้น หากคุณต้องการแทนที่แอตทริบิวต์แทนที่จะล้างค่าทิ้ง !override สามารถทำได้ และจำเป็นต้องใช้ v2.24.4 หรือใหม่กว่า