SSD Nodes Learn RAM 8GB — $66/ปี
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-01

Docker Compose volumes: bind mount หรือ named volume

เปรียบเทียบ bind mount กับ named volume ใน Docker Compose เลือกใช้กับ config และข้อมูลอย่างไร ระวังสิทธิ์ไฟล์ ตรวจสอบ สำรอง และย้ายข้อมูล พร้อมดูข้อผิดพลาดเมื่อไม่ประกาศ volumes

Bind mount หรือ named volume: คำตอบโดยย่อ

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

ทั้งสองประเภทจะอยู่ภายใต้คีย์ volumes: เดียวกันภายใน service จึงมักสับสนกัน ความแตกต่างอยู่ที่ด้านซ้ายของเครื่องหมายโคลอน หากด้านซ้ายขึ้นต้นด้วย . หรือ / แสดงว่าเป็นพาธบนโฮสต์ จึงเป็น bind mount หากเป็นค่าอื่น แสดงว่าเป็นชื่อ จึงเป็น named volume และต้องประกาศชื่อนั้นไว้ในบล็อก volumes: ระดับบนสุดด้วย

ไวยากรณ์ 2 แบบในไฟล์ 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 คือ volume ที่มีชื่อกำหนดไว้ ส่วน ./nginx.conf:/etc/nginx/nginx.conf คือ bind mount และ :ro ทำให้ mount ดังกล่าวเป็นแบบอ่านอย่างเดียว ซึ่งเป็นค่าเริ่มต้นที่เหมาะสมสำหรับไฟล์การกำหนดค่าที่ container ไม่ควรเขียนทับ หากลืมรายการ 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 mount เท่านั้น

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

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

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

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

วิธีแก้คือทำให้ตัวเลขตรงกัน ownership ระหว่าง 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 process ใช้ทำงานจริง ให้กำหนดเจ้าของไดเรกทอรีบน host ให้ตรงกับตัวเลขดังกล่าว หรือกำหนดให้ container ใช้ uid ของคุณด้วย user: "1000:1000" ใน service การกำหนด user: เหมาะกว่าสำหรับ application ที่คุณพัฒนาเอง การเปลี่ยนเจ้าของไดเรกทอรีบน host ด้วย chown ปลอดภัยกว่าสำหรับ image ที่คุณไม่ได้พัฒนาเอง เนื่องจาก image บางรายการเริ่มต้น entrypoint ในฐานะ root จากนั้นลดสิทธิ์ และคาดหวัง ownership เฉพาะในไดเรกทอรีภายใน

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

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

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

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

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

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

docker system df -v แสดง volume ทั้งหมดพร้อมขนาด และทำเครื่องหมาย volume ที่ไม่มี container ใดอ้างอิงอีกต่อไป

การตรวจสอบ volume ที่ตั้งชื่อ

volume ที่ตั้งชื่อไม่ใช่กล่องดำ ให้ Docker แสดงตำแหน่งของ volume:

docker volume inspect myapp_pgdata

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

วิธีที่ปลอดภัยในการดูข้อมูลภายในคือใช้ container ชั่วคราวที่ mount volume:

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

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

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

Bind mount เป็นไดเรกทอรีทั่วไป ดังนั้นเครื่องมือสำรองข้อมูลระดับไฟล์จึงรองรับอยู่แล้ว ระบุพาธบนโฮสต์เป็นแหล่งข้อมูลสำรองก็เสร็จสิ้น สำหรับ named volume ต้องมีขั้นตอนเพิ่มเติมอีกหนึ่งขั้น เนื่องจากเครื่องมือต้องเข้าถึงข้อมูลภายใน volume ให้ mount volume และไดเรกทอรีบนโฮสต์เข้าไปในคอนเทนเนอร์ชั่วคราวเดียวกัน แล้วเขียนเป็น 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 จะคงค่า ownership แบบตัวเลขไว้เมื่อทำงานเป็น root ภายในคอนเทนเนอร์ ซึ่งทำให้แอปพลิเคชันยังใช้งาน volume ที่กู้คืนแล้วได้

ข้อควรระวังหนึ่งประการใช้กับทั้งสองประเภท การคัดลอกไฟล์ของฐานข้อมูลขณะที่ฐานข้อมูลกำลังทำงานจะได้ archive ของข้อมูลที่เปลี่ยนแปลงอยู่ และอาจกู้คืนกลับมาในสถานะเสียหายได้ ให้หยุด service ก่อน หรือ 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 จะคง ownership, modes และ timestamps ไว้ ดังนั้นผู้ใช้ภายในคอนเทนเนอร์ที่อ่านไดเรกทอรีเดิมได้ จะยังอ่าน volume ใหม่ได้ จากนั้นเปลี่ยน service ให้ใช้ pgdata:/var/lib/postgresql/data เพิ่ม pgdata ในบล็อก volumes: ระดับบนสุด เรียกใช้ docker compose up -d และอ่าน logs ของแอปพลิเคชันก่อนลบไดเรกทอรีเดิม หากต้องการดำเนินการย้อนกลับ ให้ใช้คำสั่งเดิมโดยสลับ /from กับ /to

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

การเลือกตามแต่ละบริการ

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

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

FAQ

Bind mount และ named volume แตกต่างกันอย่างไร

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

เหตุใดจึงได้รับ "permission denied" เมื่อใช้ bind mount แต่ไม่เกิดขึ้นกับ named volume

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

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

เมื่อใช้ driver local ตามค่าเริ่มต้น volume จะอยู่ภายใต้ /var/lib/docker/volumes/<volume>/_data และ docker volume inspect <volume> จะแสดงค่า Mountpoint ที่แน่นอน อ่านค่านี้เมื่อต้องการตรวจสอบ แต่ให้เขียนข้อมูลผ่าน container เท่านั้น เพราะการแก้ไขในฐานะ root บน host อาจเปลี่ยน ownership ในลักษณะที่ 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 ทุกตัวที่ project ประกาศไว้ด้วย และการลบนี้ถาวร Bind mount จะไม่ถูกลบโดยคำสั่งใดคำสั่งหนึ่ง เนื่องจาก directory นั้นเป็นของ host ไม่ใช่ของ Docker