SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-28

วิธีใช้ Docker Compose หลายไฟล์และการรวมไฟล์ค่าคอนฟิก

เรียนรู้วิธีการรวมไฟล์ Docker Compose หลายไฟล์อย่างถูกต้อง เข้าใจลำดับการโหลดไฟล์ override อัตโนมัติ การใช้แฟล็ก -f เพื่อแยกสภาพแวดล้อม dev และ prod และวิธีแก้ปัญหาพอร์ตค้าง

สิ่งที่ Compose ทำเมื่อมีไฟล์มากกว่าหนึ่งไฟล์

Docker Compose สามารถสร้างโปรเจกต์เดียวจากไฟล์หลายไฟล์ได้ โดยจะอ่านไฟล์ตามลำดับที่ได้รับและรวมเข้าเป็นโมเดลเดียว ดังนั้นไฟล์ที่อ่านทีหลังจะเขียนทับค่าใดก็ตามที่เกิดความขัดแย้งกัน มีกลไกสองอย่างที่ทำหน้าที่นี้จากบรรทัดคำสั่ง ได้แก่ ไฟล์ override ที่ Compose โหลดเองโดยอัตโนมัติ และแฟล็ก -f ที่คุณระบุด้วยตนเอง ส่วนกลไกที่สามจะอยู่ในตัวไฟล์เองคือองค์ประกอบ include ซึ่งมีวิธีการทำงานที่แตกต่างจากสองแบบแรก

การรวมไฟล์ไม่ใช่การเขียนทับแบบธรรมดา การแมปข้อมูล (mappings) จะรวมกันทีละคีย์ ลำดับข้อมูล (sequences) จะถูกต่อท้าย และฟิลด์จำนวนหนึ่งจะถูกแทนที่ทั้งหมด ความแตกต่างนี้เองที่เป็นที่มาของความประหลาดใจ และรายการ ports คือจุดที่ทำให้เกือบทุกคนพลาด

เนื้อหาทั้งหมดด้านล่างนี้ตั้งอยู่บนสมมติฐานว่าคุณใช้ Compose v2 ซึ่งเป็นปลั๊กอิน docker compose ไม่ใช่สคริปต์ docker-compose แบบเก่า ให้รันคำสั่ง 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.24 หรือใหม่กว่า ให้ใช้คำสั่งนี้เมื่อคุณไม่สามารถแก้ไขไฟล์ฐานได้ เช่น ในกรณีที่เป็นไฟล์ fragment จากผู้ให้บริการที่คุณดึงมาใช้งาน สแต็กที่เผยแพร่จากต้นทางเป็นตัวอย่างที่ชัดเจนของกรณีนี้ ไฟล์ Compose เบื้องหลัง AFFiNE workspace แบบ self-hosted ประกาศคอนเทนเนอร์ 4 รายการที่คุณไม่ได้เป็นผู้เขียนขึ้นเอง และ !reset ช่วยให้คุณสามารถล้างแอตทริบิวต์รายการหนึ่งบนคอนเทนเนอร์เหล่านั้นได้โดยไม่ต้อง fork ไฟล์ออกมา ซึ่งจะช่วยลดภาระในการติดตามการเปลี่ยนแปลงไฟล์ดังกล่าว

การใช้ include สำหรับสแต็กที่ประกอบขึ้นจากหลายส่วน

include จะดึงแอปพลิเคชัน Compose อื่นเข้ามาในโมเดลของคุณ โดยเป็นองค์ประกอบระดับบนสุด (top-level element) ไม่ใช่แฟล็ก

include:
  - path: ../commons/compose.yaml

แต่ละพาธใน include จะถูกโหลดเป็นโมเดลแอปพลิเคชัน Compose ของตัวเอง พร้อมด้วยไดเรกทอรีโปรเจกต์ของมันเอง ดังนั้นพาธสัมพัทธ์ (relative paths) ภายในไฟล์นั้นจะอ้างอิงจากไดเรกทอรีของตัวมันเอง นี่คือความแตกต่างที่แท้จริงจาก -f และเป็นเหตุผลว่าทำไม include จึงเป็นเครื่องมือที่เหมาะสมเมื่อส่วนประกอบ (fragment) นั้นอยู่ในโฟลเดอร์อื่นหรือรีโพสิทอรีอื่น ซึ่งเป็นรูปแบบปกติของสแต็กจากผู้ให้บริการภายนอกที่คุณไม่ได้เขียนขึ้นเอง เช่น ไฟล์ Compose แบบหลายบริการที่อยู่เบื้องหลัง การติดตั้ง Authentik SSO แบบ self-hosted สามารถวางอยู่ในไดเรกทอรีของตัวเองโดยที่พาธสัมพัทธ์ยังคงทำงานได้ถูกต้อง ในขณะที่ไฟล์ของคุณยังคงเน้นเฉพาะบริการของคุณเอง

รูปแบบยาวสามารถรับตัวเลือกย่อยได้

include:
  - path:
      - ../monitoring/compose.yaml
      - ../monitoring/compose.vps.yaml
    project_directory: ../monitoring
    env_file: ../monitoring/.env

path ยอมรับรายการไฟล์ และไฟล์เหล่านั้นจะถูกรวมเข้าด้วยกันตามกฎปกติก่อนที่ผลลัพธ์จะถูกนำไปรวมในโมเดลของคุณ project_directory กำหนดพาธฐานที่ใช้สำหรับแก้ไขพาธสัมพัทธ์ในไฟล์ที่ถูกรวมเข้ามา env_file กำหนดตัวแปรของตัวเองให้กับไฟล์ที่รวมเข้ามาเพื่อใช้ในการแทนที่ค่า (interpolation) ซึ่งช่วยป้องกันไม่ให้ส่วนประกอบที่ใช้ร่วมกันอ่านค่าจาก .env ของโปรเจกต์คุณโดยไม่ตั้งใจ include ต้องการ Compose v2.20.0 หรือใหม่กว่า ตัวเลือกเดียวกันนี้ยังเหมาะสำหรับส่วนเสริมที่เป็นคอนเทนเนอร์เดี่ยวสำหรับสแต็กที่คุณใช้งานอยู่แล้ว เช่น Halcyon ซึ่งปรับหน้าตาคลัง Jellyfin ให้เหมือนร้านเช่าวิดีโอในยุค 90 โดยไฟล์ของมันจะเก็บแท็กอิมเมจและ env_file ของตัวเองไว้ ดังนั้นการอัปเกรดจึงไม่จำเป็นต้องไปแก้ไขไฟล์ที่สแต็กสื่อของคุณใช้งานอยู่

หากชื่อทรัพยากรซ้ำกันระหว่างไฟล์ของคุณและไฟล์ที่รวมเข้ามา ระบบจะรายงานเป็นข้อผิดพลาดแทนที่จะรวมเข้าด้วยกันโดยเงียบๆ ซึ่งเป็นความตั้งใจในการออกแบบ หากต้องการเปลี่ยนแปลงสิ่งที่ไฟล์ที่รวมเข้ามาประกาศไว้ ให้ใส่การเปลี่ยนแปลงนั้นไว้ใน compose.override.yaml: การแทนที่ (override) จะถูกนำไปใช้กับโมเดลที่ประกอบเสร็จแล้ว จึงสามารถแก้ไขทรัพยากรที่รวมเข้ามาได้โดยไม่เกิดความขัดแย้งกัน แนวทางนี้มีประโยชน์อย่างยิ่งกับสแต็กที่ไฟล์ต้นทางถูกเขียนทับทุกครั้งที่มีการปล่อยเวอร์ชันใหม่ เช่น เซิร์ฟเวอร์รูปภาพแบบหลายคอนเทนเนอร์ที่เปรียบเทียบไว้ใน PhotoPrism กับ Immich ซึ่งการผูกพอร์ต localhost หรือการเพิ่ม volume ควรอยู่ในไฟล์ override ของคุณ แทนที่จะอยู่ในไฟล์ที่จะถูกแทนที่ในการอัปเกรดครั้งถัดไป

สรุปสั้นๆ: include ใช้สำหรับรวมแอปพลิเคชันแยกต่างหากเข้าด้วยกัน ส่วน -f ใช้สำหรับวางเลเยอร์การตั้งค่าทับลงบนแอปพลิเคชันเดียว

การแยกสภาพแวดล้อม 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 ps

ps ควรแสดงรายการบริการทั้งสองว่ากำลังทำงานอยู่ โดยที่ 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.24 หรือใหม่กว่า ให้เขียน ports: !reset [] หรือ MY_VAR: !reset null ในไฟล์ที่ใช้เขียนทับ แล้วแอตทริบิวต์นั้นจะกลับไปเป็นค่าเริ่มต้นหรือเป็น null ค่าที่คุณระบุให้กับแท็กนั้นจำเป็นต้องมีแต่จะถูกละเว้น หากคุณต้องการแทนที่แอตทริบิวต์แทนที่จะล้างค่าทิ้ง ให้ใช้ !override ซึ่งต้องการเวอร์ชัน v2.24.4 หรือใหม่กว่า