← กลับ
DockerVolumeStorageTroubleshooting

Volume / Disk ใน Container เพี้ยนเพราะอะไร (bind mount vs volume)

ข้อมูลใน container หาย / permission denied / write ช้า — เลือก bind mount, named volume, tmpfs ให้ถูกกับงาน เข้าใจกับดักของแต่ละแบบ

2026-01-25อ่าน 7 นาทีใหม่

ปัญหาที่เจอ

  • Container ตาย → ข้อมูลใน DB หาย
  • Mount โฟลเดอร์จาก host → permission denied
  • File เขียนใน container แต่ host ไม่เห็น (หรือกลับกัน)
  • Write ใน volume ช้ากว่า write ใน container ตรงๆ

ปัญหาเหล่านี้มาจากการเข้าใจ "storage type" ของ Docker ไม่ครบ

3 ประเภท Storage ใน Docker

┌─────────────────────────────────────┐
│  Container (writable layer)         │ ← หายเมื่อลบ container
└─────────────────────────────────────┘
   │      │       │
   ▼      ▼       ▼
[bind] [volume] [tmpfs]
folder /var/...  RAM
host    /docker/  

1. Container Writable Layer (default)

ทุก container มี layer เขียนได้ของตัวเอง — เก็บใน /var/lib/docker/overlay2/.../diff/

ทุกอย่างที่ container เขียนโดยไม่ mount = อยู่ใน layer นี้

ปัญหา:

  • ลบ container → ข้อมูลหาย
  • Performance ช้ากว่า volume (overlay filesystem)
  • ขนาดใหญ่ขึ้นเรื่อยๆ ทำ disk เต็ม

ใช้กับ: ไฟล์ชั่วคราว, build artifact ที่ไม่สำคัญ

2. Named Volume

Docker จัดการที่อยู่ + lifecycle ให้

services:
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

หรือ command line:

docker volume create pgdata
docker run -v pgdata:/var/lib/postgresql/data postgres

ที่อยู่จริง: /var/lib/docker/volumes/pgdata/_data/

ข้อดี:

  • คงอยู่หลังลบ container
  • Permission/ownership Docker จัดการ
  • Performance เท่า host filesystem
  • Backup/migrate ง่าย

ใช้กับ: data ของ database, cache, app data — ใช้เป็นหลัก

3. Bind Mount

Mount path ของ host ตรงๆ:

services:
  app:
    volumes:
      - ./src:/usr/src/app
      - /var/log/myapp:/var/log/myapp

ข้อดี:

  • Host เห็นไฟล์ที่ container เขียน (ดีกับ dev — hot reload)
  • Path control ได้เอง

ข้อเสีย:

  • Permission ระหว่าง host user กับ container user ต้องตรงกัน — ปัญหาที่เจอบ่อยสุด
  • Performance ลดบน macOS / Windows (ผ่าน VM)
  • Path hardcode — ย้าย server ลำบาก

ใช้กับ: dev environment (source code), config file, log directory ที่ต้องการ access จาก host

4. tmpfs (เก็บใน RAM)

services:
  app:
    tmpfs:
      - /tmp
      - /run:size=64m

ข้อดี:

  • เร็วที่สุด (RAM)
  • Container ตาย ข้อมูลหายแน่นอน

ใช้กับ: secret ชั่วคราว, cache ที่ไม่จำเป็นต้อง persist

ปัญหาที่เจอบ่อย (พร้อมวิธีแก้)

Permission denied ใน bind mount

$ docker run -v $(pwd):/app node npm install
EACCES: permission denied, mkdir '/app/node_modules'

สาเหตุ: container รันด้วย user node (UID 1000) — แต่ host folder owned by user อื่น

วิธี 1: รันด้วย user ของ host

docker run -v $(pwd):/app -u $(id -u):$(id -g) node npm install

วิธี 2: chown ใน Dockerfile

FROM node:20-alpine
RUN addgroup -g 1000 -S app && adduser -u 1000 -S app -G app
USER app

วิธี 3: ใช้ named volume แทน bind mount

services:
  app:
    volumes:
      - ./src:/app/src           # source code (read-only ก็ได้)
      - node_modules:/app/node_modules   # Docker จัดการ permission
volumes:
  node_modules:

node_modules หายเมื่อ bind mount

services:
  app:
    volumes:
      - .:/app           # bind mount ทุกอย่าง รวม node_modules ของ host (อาจไม่มี)

docker run -v $(pwd):/app ทับ node_modules ใน image

แก้: mask node_modules ด้วย named volume:

volumes:
  - .:/app
  - /app/node_modules    # anonymous volume mask folder นี้

Docker จะใช้ /app/node_modules ที่อยู่ใน image แทน folder จาก host

Database data หายหลัง down -v

docker compose down -v

-v = ลบ volume ด้วย → data ใน Postgres หาย

อย่าใช้ -v ใน production — ใช้:

docker compose down       # ไม่ลบ volume

Write ช้าบน macOS / Windows

bind mount ผ่าน VM (HyperKit / WSL) → write ผ่าน 2 layer ช้า 5-10 เท่า

แก้:

  • ใช้ named volume สำหรับ node_modules, build cache
  • ใช้ Docker Desktop option VirtioFS (macOS) — เร็วขึ้น 2-3 เท่า
  • บน Windows ใช้ WSL2 + เก็บ source ใน /home/... ไม่ใช่ /mnt/c/...

Backup volume

# backup volume เป็น tar
docker run --rm \
  -v pgdata:/data \
  -v $(pwd):/backup \
  busybox tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .

# restore
docker run --rm \
  -v pgdata:/data \
  -v $(pwd):/backup \
  busybox tar xzf /backup/pgdata-2026-01-15.tar.gz -C /data

หรือใช้ tool เฉพาะ database — pg_dump ดีกว่า raw filesystem backup

ดูว่า volume กิน disk เท่าไหร่

docker system df -v | grep -A 50 "Local Volumes"

หรือ:

sudo du -sh /var/lib/docker/volumes/*/ | sort -hr | head

Inspect ว่า volume อยู่ไหน

docker volume inspect pgdata
[
    {
        "Name": "pgdata",
        "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
        "Driver": "local",
        ...
    }
]

เข้าไปดูตรงๆ:

sudo ls -la /var/lib/docker/volumes/pgdata/_data/

Volume Driver — เกินกว่า local

Docker รองรับ volume driver อื่น:

  • nfs — mount NFS share
  • cifs — mount Windows share
  • cloudstor — AWS EFS, GCP Filestore
  • rexray — multi-cloud volumes

ตัวอย่าง NFS:

volumes:
  shared:
    driver: local
    driver_opts:
      type: nfs
      o: addr=10.0.0.5,nfsvers=4
      device: ":/exported/path"

Volume vs Bind Mount — เลือกยังไง

| Use Case | เลือก | |---|---| | Database data | Named volume | | Cache (Redis) | Named volume | | App data ที่ต้อง persist | Named volume | | Source code (dev) | Bind mount | | Config file (/etc/app/config.yml) | Bind mount read-only | | Log directory | Bind mount หรือ centralized log | | Secret ชั่วคราว | tmpfs | | /tmp, /run | tmpfs |

Best Practice

1. ใช้ named volume สำหรับ data

ไม่ bind mount /var/lib/postgresql/data จาก host:

# ❌
volumes:
  - /opt/pgdata:/var/lib/postgresql/data

# ✅
volumes:
  - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:

2. Read-only ถ้าไม่ต้องเขียน

volumes:
  - ./config:/etc/app:ro

ป้องกัน container แก้ config ของ host

3. แยก dev compose กับ prod

docker-compose.override.yml (dev — auto-load):

services:
  app:
    volumes:
      - ./src:/app/src       # bind mount เพื่อ hot reload

docker-compose.prod.yml:

services:
  app:
    image: ghcr.io/me/myapp:1.0   # ใช้ image ที่ build มาแล้ว — ไม่ต้อง bind mount

4. Label volume ให้ดู

volumes:
  pgdata:
    labels:
      project: "myapp"
      environment: "production"
      backup: "daily"

ดูเพื่อตอน:

docker volume ls --filter label=project=myapp

Cheat Sheet

# list volume
docker volume ls

# create
docker volume create myvol

# inspect (ดูตำแหน่งจริง)
docker volume inspect myvol

# ลบ volume เดี่ยว
docker volume rm myvol

# ลบ volume ที่ไม่มี container ใช้
docker volume prune

# ลบ volume ของ project compose ทั้งหมด (ระวัง!)
docker compose down -v

# backup
docker run --rm -v myvol:/data -v $(pwd):/backup busybox \
  tar czf /backup/myvol.tar.gz -C /data .

สรุป

  • Container layer = ชั่วคราว, ลบ container แล้วหาย
  • Named volume = ที่เก็บข้อมูลหลักของ production
  • Bind mount = dev / config / share file ระหว่าง host กับ container
  • tmpfs = RAM, secret ชั่วคราว

ปัญหา 90%:

  • Permission ใน bind mount → ใช้ -u flag หรือ named volume
  • Data หาย → อย่า down -v, ใช้ named volume
  • Performance ช้า → ใช้ named volume (ไม่ใช่ bind) สำหรับ heavy I/O

อ่านต่อ: Docker Disk Usage — ตรวจขนาด volume

← ดูบทความอื่น