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

Docker Compose เลือกใช้ Bind Mount หรือ Named Volume ดี

เปรียบเทียบความแตกต่างระหว่าง Bind mount และ Named volume ใน Docker Compose พร้อมวิธีเลือกใช้งานให้เหมาะสมกับข้อมูลแต่ละประเภท ปัญหาเรื่องสิทธิ์ไฟล์ และแนวทางการสำรองข้อมูล

Bind mount หรือ named volume: คำตอบสั้นๆ

Docker Compose volumes มีอยู่ 2 ประเภท โดยการเลือกใช้ขึ้นอยู่กับว่าใครเป็นเจ้าของไฟล์นั้น ให้ใช้ bind mount สำหรับไฟล์ที่คุณเขียนและอ่านเอง เช่น ไฟล์ตั้งค่า, เทมเพลต หรือเว็บไซต์แบบ static ให้ใช้ named volume สำหรับข้อมูลที่แอปพลิเคชันเป็นเจ้าของ เช่น ไฟล์ฐานข้อมูล, ดัชนีการค้นหา หรือสื่อที่อัปโหลดเข้ามา Bind mount จะชี้ไปยัง path บน host ที่คุณสามารถเปิดแก้ไขได้ด้วยโปรแกรมแก้ไขข้อความ ส่วน named volume คือพื้นที่จัดเก็บที่ Docker สร้างและติดตามให้คุณ ซึ่งคุณจะเข้าถึงได้ผ่านทาง Docker เท่านั้น

ทั้งสองประเภทจะปรากฏอยู่ภายใต้คีย์ volumes: เดียวกันภายใน service ซึ่งเป็นเหตุผลว่าทำไมจึงมักสับสนกัน ความแตกต่างอยู่ที่ฝั่งซ้ายของเครื่องหมายทวิภาค (colon) หากฝั่งซ้ายขึ้นต้นด้วย . หรือ / นั่นคือ host path ซึ่งถือเป็น bind mount หากเป็นชื่ออื่นที่ไม่ใช่ path จะถือว่าเป็นชื่อของ named volume และชื่อนั้นจะต้องถูกประกาศไว้ในบล็อก volumes: ที่ระดับบนสุดด้วย

ไวยากรณ์สองรูปแบบในไฟล์ compose

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: changeme
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: nginx:1.27
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./site:/usr/share/nginx/html:ro

volumes:
  pgdata:

pgdata:/var/lib/postgresql/data คือ named volume ส่วน ./nginx.conf:/etc/nginx/nginx.conf คือ bind mount และ :ro จะทำการ mount แบบอ่านได้อย่างเดียว ซึ่งเป็นค่าเริ่มต้นที่เหมาะสมสำหรับไฟล์คอนฟิกที่คอนเทนเนอร์ไม่ควรแก้ไข หากลืมระบุรายการ volumes: ไว้ที่ระดับบนสุด Compose จะหยุดทำงานพร้อมกับข้อผิดพลาด service "db" refers to undefined volume pgdata

สั่งเริ่มการทำงานและแสดงรายการสิ่งที่ Docker สร้างขึ้น:

docker compose up -d
docker volume ls

volume ดังกล่าวไม่ได้ชื่อ pgdata แต่มีชื่อว่า <project>_pgdata โดยชื่อโปรเจกต์จะใช้ชื่อของไดเรกทอรีที่เก็บไฟล์ compose เป็นค่าเริ่มต้น หากไดเรกทอรีชื่อ myapp จะได้ชื่อเป็น myapp_pgdata เรื่องนี้มีความสำคัญเพราะหากเปลี่ยนชื่อไดเรกทอรี คุณจะได้ volume ใหม่ที่ว่างเปล่า และแอปพลิเคชันจะดูเหมือนข้อมูลสูญหาย ทั้งที่ความจริงแล้วข้อมูลไม่ได้หายไปไหน แต่ volume เก่าจะยังคงแสดงอยู่ในรายการของ docker volume ls คุณสามารถกำหนดชื่อให้คงที่ด้วย name: ในไฟล์ compose หรือตั้งค่า COMPOSE_PROJECT_NAME หากมีความเป็นไปได้ที่ไดเรกทอรีจะถูกย้าย การตั้งค่าลักษณะนี้ควรจัดเก็บไว้พร้อมกับ ไฟล์สภาพแวดล้อมและข้อมูลลับของ Compose อื่นๆ ของคุณ

เหตุใดข้อผิดพลาดด้านสิทธิ์จึงเกิดขึ้นเฉพาะกับ bind mounts

นี่คือความแตกต่างในทางปฏิบัติที่สำคัญที่สุด ซึ่งเกิดจากกฎข้อเดียวคือ: named volume ที่ว่างเปล่าในการใช้งานครั้งแรกจะถูกเติมข้อมูลจาก image โดยอัตโนมัติ แต่ bind mount จะไม่ทำเช่นนั้น

เมื่อ Docker ทำการ mount named volume ที่ว่างเปล่าทับไดเรกทอรีที่มีข้อมูลอยู่แล้วใน image ระบบจะคัดลอกข้อมูลเหล่านั้นลงใน volume โดยคงความเป็นเจ้าของและโหมดตามที่ image กำหนดไว้ ตัวอย่างเช่น official Postgres image จะจัดส่ง /var/lib/postgresql/data ซึ่งเป็นเจ้าของโดยผู้ใช้ postgres ของตัวมันเอง ดังนั้น volume ที่ได้จึงมีเจ้าของเป็น numeric id เดียวกัน และฐานข้อมูลจึงเริ่มทำงานได้

ในทางกลับกัน bind mount จะทำงานตรงกันข้าม สิ่งที่อยู่บน host คือสิ่งที่ container จะมองเห็น รวมถึงความเป็นเจ้าของด้วย และเนื้อหาของ image ที่อยู่ใน path นั้นจะถูกซ่อนไว้ หากไดเรกทอรีบน host ไม่มีอยู่จริง Docker daemon จะสร้างขึ้นมาให้ และเนื่องจาก daemon ทำงานในฐานะ root คุณจึงได้ไดเรกทอรีที่มีเจ้าของเป็น root:root ส่งผลให้กระบวนการของ container ที่ทำงานด้วยผู้ใช้ที่ไม่ใช่ root ไม่สามารถเขียนข้อมูลลงไปได้:

PermissionError: [Errno 13] Permission denied: '/data/app.db'

วิธีแก้ไขคือการทำให้ตัวเลขตรงกัน ความเป็นเจ้าของผ่าน bind mount จะถูกเปรียบเทียบด้วย numeric user id ไม่ใช่ชื่อผู้ใช้ เนื่องจาก container มี /etc/passwd เป็นของตัวเอง ผู้ใช้ที่ชื่อ app ภายใน container จึงไม่มีความหมายบน host แต่ uid 1000 หมายถึง uid 1000 ทั้งสองฝั่ง

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

docker compose exec web id จะแสดง uid ที่กระบวนการของ container ใช้งานจริง คุณควรปรับไดเรกทอรีบน host ให้ตรงกับตัวเลขนั้น หรือกำหนดให้ container ใช้เลข uid ของคุณด้วย user: "1000:1000" ใน service การกำหนด user: เป็นวิธีที่สะอาดกว่าสำหรับแอปพลิเคชันที่คุณเขียนเอง ส่วนการใช้ chown กับไดเรกทอรีบน host จะปลอดภัยกว่าสำหรับ image ที่คุณไม่ได้เขียนขึ้นเอง เนื่องจากบาง image จะเริ่ม entrypoint ด้วยสิทธิ์ root จากนั้นจึงลดสิทธิ์ลง และคาดหวังความเป็นเจ้าของที่เฉพาะเจาะจงในไดเรกทอรีนั้น

ยังมีอีกสองประเด็นที่ควรทราบ บน Fedora, RHEL และระบบอื่นๆ ที่มีการบังคับใช้ SELinux (security-enhanced Linux) การทำ bind mount จะถูกปฏิเสธจนกว่าจะมีการเปลี่ยน label ดังนั้นให้เพิ่ม :z สำหรับ path ที่แชร์ระหว่าง container หรือ :Z สำหรับ path ที่มีเพียง container เดียวควรเข้าถึงได้ โดยเขียนในรูปแบบ - ./data:/data:Z นอกจากนี้ การทำ bind mount เฉพาะไฟล์แทนที่จะเป็นไดเรกทอรีจะเกิดปัญหาเมื่อโปรแกรมแก้ไขไฟล์ทำการแทนที่ไฟล์เดิมแทนที่จะเขียนทับลงไปโดยตรง เพราะการ mount จะอ้างอิงตาม inode เดิม ทำให้ container ยังคงเห็นเนื้อหาเก่าจนกว่าคุณจะรีสตาร์ท container นั้น หากมีการแก้ไขไฟล์บ่อยครั้ง ให้ mount ที่ไดเรกทอรีแม่แทน

ประสิทธิภาพ: ความแตกต่างที่แท้จริง

บนเซิร์ฟเวอร์ Linux ทั้งสองรูปแบบจะผ่าน kernel path เดียวกัน ดังนั้นความแตกต่างของ throughput จึงน้อยมากจนไม่ควรนำมาเป็นเกณฑ์ในการตัดสินใจ Named volumes ที่ใช้ driver เริ่มต้น local จะอยู่ในระบบไฟล์เดียวกับส่วนที่เหลือของ Docker ภายใต้ /var/lib/docker/volumes/ ในขณะที่ bind mount จะอยู่ในตำแหน่งที่คุณระบุไว้

ความแตกต่างจะปรากฏชัดเจนบน Docker Desktop สำหรับ macOS และ Windows ซึ่งคอนเทนเนอร์จะทำงานอยู่ภายใน virtual machine ในกรณีนั้น bind mount จะต้องข้ามจากระบบไฟล์ของ host เข้าไปยัง virtual machine ผ่านชั้นการแชร์ไฟล์ ทำให้ภาระงานที่มีการดำเนินการกับไฟล์ขนาดเล็กจำนวนมาก เช่น dependency tree ของ Node.js หรือ cache ของ PHP framework ทำงานช้าลงอย่างเห็นได้ชัด ส่วน named volumes จะคงอยู่ภายใน virtual machine จึงไม่มีค่าใช้จ่ายในส่วนนี้ นี่คือเหตุผลที่ไฟล์ compose สำหรับการพัฒนาจำนวนมากใช้ bind-mount กับไดเรกทอรีซอร์สโค้ด แต่ประกาศ named volume ทับบน node_modules

ความแตกต่างที่แท้จริงอีกประการหนึ่งคือตำแหน่งที่จัดเก็บข้อมูล Bind mount ไปยัง /mnt/backup จะวางข้อมูลไว้บนดิสก์ลูกนั้น ส่วน named volume จะลงไปอยู่ในระบบไฟล์ใดก็ตามที่ถือครอง /var/lib/docker ซึ่งบน VPS มักจะเป็น root disk ฐานข้อมูลที่ขยายขนาดขึ้นภายใน named volume จะทำให้ดิสก์ลูกเดียวกับที่เก็บ system log เต็มได้ ควรตรวจสอบก่อนที่จะกลายเป็นเหตุการณ์ไม่คาดฝัน:

docker system df -v
df -h /var/lib/docker

docker system df -v จะแสดงรายการ volume ทั้งหมดพร้อมขนาด และระบุ volume ที่ไม่มีคอนเทนเนอร์ใดอ้างอิงถึงแล้ว

การตรวจสอบ named volume

Named volume ไม่ใช่กล่องดำ คุณสามารถสอบถาม Docker ได้ว่าข้อมูลอยู่ที่ใด:

docker volume inspect myapp_pgdata

ฟิลด์ Mountpoint จะแสดง path จริงบนโฮสต์ ซึ่งโดยปกติคือ /var/lib/docker/volumes/myapp_pgdata/_data คุณสามารถอ่านไฟล์ได้ด้วย sudo ls ซึ่งมีประโยชน์สำหรับการตรวจสอบอย่างรวดเร็ว แต่อย่าแก้ไขไฟล์ในตำแหน่งนี้โดยตรง การเขียนไฟล์ด้วยสิทธิ์ root จะทำให้เกิดปัญหาเรื่องความเป็นเจ้าของไฟล์ตามที่กล่าวไว้ข้างต้น และ path ดังกล่าวเป็นรายละเอียดเฉพาะของไดรเวอร์ local ซึ่งไดรเวอร์ volume อื่นอาจไม่มีเหมือนกัน

วิธีที่ปลอดภัยในการตรวจสอบภายในคือการใช้ container แบบชั่วคราวเพื่อ mount volume นั้น:

docker run --rm -v myapp_pgdata:/vol alpine ls -la /vol

วิธีนี้ใช้ได้กับทุกไดรเวอร์ โดยจะเห็นสิทธิ์การเข้าถึงไฟล์เหมือนกับที่ container จริงเห็น และไม่ทิ้งสิ่งใดไว้เบื้องหลังเนื่องจากมีการใช้ --rm

การสำรองข้อมูลแต่ละประเภท

Bind mount เป็นไดเรกทอรีทั่วไป ดังนั้นเครื่องมือสำรองข้อมูลระดับไฟล์จึงรองรับอยู่แล้ว เพียงแค่ระบุ path บนโฮสต์ให้เครื่องมือสำรองข้อมูลก็เสร็จสิ้น ส่วน Named volume จำเป็นต้องมีขั้นตอนเพิ่มอีกหนึ่งขั้นเนื่องจากเครื่องมือต้องเข้าถึงข้อมูลภายใน volume นั้น ให้ mount volume และไดเรกทอรีบนโฮสต์เข้าไปใน container ชั่วคราวตัวเดียวกัน จากนั้นจึงเขียนไฟล์ archive:

docker run --rm \
  -v myapp_pgdata:/data:ro \
  -v "$PWD":/backup \
  alpine tar czf /backup/pgdata.tar.gz -C /data .

การกู้คืนข้อมูลทำได้โดยการย้อนกระบวนการกลับไปยัง volume ใหม่:

docker volume create myapp_pgdata_restored
docker run --rm \
  -v myapp_pgdata_restored:/data \
  -v "$PWD":/backup \
  alpine tar xzf /backup/pgdata.tar.gz -C /data

tar จะรักษาความเป็นเจ้าของแบบตัวเลข (numeric ownership) ไว้เมื่อรันในฐานะ root ภายใน container ซึ่งเป็นสิ่งที่ทำให้แอปพลิเคชันสามารถใช้งาน volume ที่กู้คืนมาได้

มีข้อควรระวังหนึ่งประการที่ใช้กับทั้งสองประเภท การคัดลอกไฟล์ของฐานข้อมูลในขณะที่ฐานข้อมูลกำลังทำงานอยู่จะทำให้ได้ archive ของข้อมูลที่เปลี่ยนแปลงตลอดเวลา ซึ่งอาจกู้คืนกลับมาในสถานะที่เสียหายได้ ให้หยุดบริการก่อน หรือใช้เครื่องมือของฐานข้อมูลเองในการ dump ข้อมูล ดังที่แสดงใน docker compose exec -T db pg_dump -U postgres appdb > appdb.sql วิธีนี้จะได้ไฟล์ธรรมดาที่คุณสามารถนำไปรวมไว้ใน กระบวนการสำรองข้อมูลด้วย restic ที่เข้ารหัส ควบคู่ไปกับไฟล์ compose ของคุณได้

การย้าย bind mount ไปยัง named volume

การย้ายข้อมูลนี้เป็นการคัดลอก ไม่ใช่การเปลี่ยนชื่อ และใช้เวลาดำเนินการประมาณ 1 นาที

docker compose down
docker volume create myapp_pgdata
docker run --rm \
  -v "$PWD/data":/from \
  -v myapp_pgdata:/to \
  alpine sh -c 'cp -a /from/. /to/'

cp -a จะคงความเป็นเจ้าของ โหมด และ timestamp ไว้ ดังนั้นผู้ใช้ใน container ที่เคยอ่านไดเรกทอรีเดิมได้จะยังคงอ่าน volume ใหม่ได้เช่นเดิม จากนั้นให้แก้ไข service เพื่อใช้ pgdata:/var/lib/postgresql/data เพิ่ม pgdata ลงในบล็อก volumes: ระดับบนสุด รันคำสั่ง docker compose up -d และตรวจสอบ log ของแอปพลิเคชันก่อนที่คุณจะลบไดเรกทอรีเดิมทิ้ง การย้ายในทิศทางตรงกันข้ามจะใช้คำสั่งเดียวกันโดยสลับ /from และ /to

โปรดจำไว้หนึ่งเรื่องในระหว่างการทดสอบ docker compose down จะไม่ยุ่งกับ named volume แต่ docker compose down -v จะลบทุก named volume ที่โปรเจกต์ประกาศไว้ทิ้งทั้งหมดและไม่สามารถย้อนกลับได้ ในขณะที่ bind mount จะยังคงอยู่ทั้งสองกรณีเนื่องจาก Docker ไม่เคยเป็นเจ้าของไดเรกทอรีนั้น หากคุณยังไม่คุ้นเคยกับคำสั่งจัดการวงจรชีวิตของคอนเทนเนอร์ คู่มือพื้นฐาน Docker Compose สำหรับ VPS จะช่วยอธิบายขั้นตอนเหล่านี้ให้คุณเข้าใจ

การเลือกใช้สำหรับแต่ละบริการ

ให้พิจารณาว่าใครเป็นผู้เขียนไฟล์นั้น หากเป็นไฟล์คอนฟิกูเรชันที่คุณแก้ไขผ่านโปรแกรมแก้ไขข้อความและบันทึกไว้ใน git ให้ใช้ bind mount โดยเมาท์ไว้ที่ :ro เนื่องจากคุณต้องการให้ไฟล์นั้นมองเห็นได้และมีการทำเวอร์ชันควบคุมไว้ ส่วนสถานะของแอปพลิเคชันที่คุณไม่เคยเปิดแก้ไขด้วยตนเองนั้นควรอยู่ใน named volume เนื่องจาก Docker จะกำหนดสิทธิ์การเข้าถึงได้อย่างถูกต้องและข้อมูลนั้นไม่ได้ขึ้นอยู่กับพาธบนโฮสต์

กรณีที่เป็นสื่อบันทึกข้อมูลจะเป็นแบบผสม คลังรูปภาพเป็นสิ่งที่แอปพลิเคชันเขียนข้อมูลลงไปแต่คุณก็เป็นผู้จัดการด้วยเช่นกัน และมักจะมีขนาดใหญ่จนจำเป็นต้องใช้ดิสก์เฉพาะ ให้ใช้ bind mount ไปยังพาธบนดิสก์นั้นและกำหนดความเป็นเจ้าของไฟล์อย่างชัดเจนเพียงครั้งเดียว นี่คือรูปแบบที่ stack สำหรับ self-host ส่วนใหญ่เลือกใช้ นั่นคือใช้ named volume สำหรับฐานข้อมูลและแคช และใช้ bind mount สำหรับไฟล์คอนฟิกูเรชันและไดเรกทอรีขนาดใหญ่ที่คุณต้องการดูแลเป็นพิเศษ บริการสนับสนุนลูกค้าอย่าง Chatwoot ที่รันบน VPS ก็ใช้รูปแบบนี้เช่นกัน โดยเก็บ Postgres ไว้ใน named volume และเก็บไฟล์แนบที่อัปโหลดไว้ในพาธที่คุณสามารถกำหนดจุดสำรองข้อมูลได้

FAQ

Bind mount กับ named volume ต่างกันอย่างไร?

Bind mount คือการแมป path บน host เข้าไปใน container ทำให้ทั้งสองฝั่งเห็น directory เดียวกันและคุณสามารถแก้ไขไฟล์ด้วยเครื่องมือปกติได้ ส่วน named volume คือพื้นที่จัดเก็บที่ Docker สร้างและจัดการขึ้นมา โดยอ้างอิงผ่านชื่อและประกาศไว้ในบล็อก volumes: ที่ระดับบนสุด ความแตกต่างในทางปฏิบัติคือเรื่องความเป็นเจ้าของ: ให้ใช้ bind mount สำหรับไฟล์ config ที่คุณดูแลเอง และใช้ named volume สำหรับข้อมูลที่แอปพลิเคชันเป็นผู้จัดการ

ทำไมถึงเจอข้อผิดพลาด "permission denied" เมื่อใช้ bind mount แต่ไม่เจอใน named volume?

Named volume ที่ว่างเปล่าจะถูกตั้งค่าเริ่มต้นจาก image ทำให้ได้รับสิทธิ์ความเป็นเจ้าของตามที่ image กำหนดไว้และ user ใน container สามารถเขียนข้อมูลได้ แต่ bind mount จะแสดง directory บน host ตามสภาพจริง หาก Docker ต้องสร้าง directory นั้นขึ้นมา มันจะถูกกำหนดให้ root เป็นเจ้าของ ให้รัน docker compose exec <service> id เพื่อดู numeric id ที่ container ใช้งาน จากนั้นใช้ sudo chown -R <uid>:<gid> กับ directory บน host หรือตั้งค่า user: "1000:1000" ใน service

Docker จัดเก็บ named volume ไว้ที่ไหนบนดิสก์?

หากใช้ driver เริ่มต้นอย่าง local ข้อมูลจะถูกเก็บไว้ภายใต้ /var/lib/docker/volumes/<volume>/_data และ docker volume inspect <volume> จะแสดง Mountpoint ที่แท้จริงออกมา คุณสามารถอ่านไฟล์ได้หากต้องการตรวจสอบข้อมูล แต่ควรเขียนข้อมูลผ่าน container เท่านั้น เนื่องจากการแก้ไขไฟล์ในฐานะ root บน host จะทำให้ความเป็นเจ้าของไฟล์เปลี่ยนไปในรูปแบบที่ container ไม่คาดคิด

จะสำรองข้อมูล named volume ได้อย่างไร?

ให้รัน container ชั่วคราวโดย mount ทั้ง volume และ directory บน host เข้าไป จากนั้นทำ archive ข้อมูลจากที่หนึ่งไปยังอีกที่หนึ่งด้วย docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data . สำหรับฐานข้อมูล ให้ใช้เครื่องมือของฐานข้อมูลนั้นๆ ในการ dump ข้อมูลแทนการคัดลอกไฟล์โดยตรง เพราะการคัดลอกไฟล์ขณะที่มีการเขียนข้อมูลอาจทำให้ข้อมูลที่กู้คืนมาเสียหายได้

คำสั่ง docker compose down จะลบ volume ของฉันหรือไม่?

docker compose down จะลบ container และ network ออก แต่จะคง named volume ไว้ ส่วน docker compose down -v จะลบทุก named volume ที่ประกาศไว้ในโปรเจกต์ทิ้งอย่างถาวร สำหรับ bind mount จะไม่ถูกลบโดยคำสั่งใดๆ ทั้งสิ้น เนื่องจาก directory ดังกล่าวเป็นของ host ไม่ใช่ของ Docker