Volumes và Bind Mount: Lưu Dữ Liệu Bền Vững
Nhớ lại Bài 2: mọi thứ container ghi ra nằm ở writable layer, và writable layer biến mất khi container bị xóa. Vậy database, file người dùng upload, log cần giữ thì sao? Câu trả lời là đưa dữ liệu ra ngoài container — bằng volume hoặc bind mount. Đó là chủ đề bài này.
Vấn đề: dữ liệu chết theo container
Thử nhanh để thấy vấn đề. Chạy một container, ghi một file, rồi xóa container:
docker run --name tmp alpine sh -c 'echo "quan trong" > /data.txt'
docker rm tmp
File /data.txt nằm trong writable layer của container tmp. Khi rm, nó mất luôn. Với database thì đây là thảm họa. Ta cần chỗ lưu tách khỏi vòng đời container.
Docker có hai cách chính (cùng một tmpfs cho dữ liệu tạm trong RAM):
Container
┌─────────────────────────────┐
│ Writable layer (chết theo │ ← KHÔNG để dữ liệu cần giữ ở đây
│ container) │
│ │
│ /data ───┐ /app ───┐ │
└────────────┼───────────┼─────┘
│ │
┌───────▼──┐ ┌────▼──────────┐
│ Volume │ │ Bind mount │
│ (Docker │ │ (đường dẫn │
│ quản lý)│ │ trên host) │
└──────────┘ └───────────────┘
/var/lib/docker/... /home/ban/app
Named volume: để Docker quản lý
Theo tài liệu, volume là cơ chế lưu trữ bền vững do Docker daemon quản lý, và "lý tưởng cho xử lý dữ liệu cần hiệu năng và lưu trữ lâu dài". Docker cất dữ liệu volume trong vùng riêng của nó trên host, bạn không cần quan tâm đường dẫn cụ thể.
Tạo một volume và ghi dữ liệu vào nó qua một container:
docker volume create demo-data
docker run --rm -v demo-data:/data alpine sh -c 'echo "du lieu quan trong" > /data/note.txt'
Cú pháp -v demo-data:/data nghĩa là: gắn volume demo-data vào đường dẫn /data bên trong container. Container này dùng --rm nên xóa ngay sau khi chạy. Giờ tạo một container hoàn toàn mới gắn cùng volume:
docker run --rm -v demo-data:/data alpine cat /data/note.txt
du lieu quan trong
Dữ liệu vẫn còn, dù container ghi ra nó đã bị xóa từ lâu. Volume sống độc lập với container — đây chính là tính bền vững ta cần.
Xem volume nằm ở đâu:
docker volume inspect demo-data --format '{{.Mountpoint}}'
/var/lib/docker/volumes/demo-data/_data
(Trên macOS/Windows đường dẫn này nằm trong VM Linux — nhớ Bài 1.)
Quản lý volume:
docker volume ls # liệt kê
docker volume inspect <ten> # chi tiết
docker volume rm <ten> # xóa
docker volume prune # xóa mọi volume không gắn container nào
Cẩn thận với
volume prunevàrm: volume chứa dữ liệu thật (database...). Xóa là mất. Đây là lý do volume tách khỏi container: bạn xóa container thoải mái, nhưng volume thì xóa có chủ đích.
Bind mount: gắn thẳng thư mục host
Theo tài liệu, bind mount tạo liên kết trực tiếp giữa một đường dẫn trên host và container, và nên dùng "khi bạn cần truy cập file từ cả container lẫn host". Khác với volume (Docker quản lý vùng lưu), bind mount trỏ tới đúng một thư mục bạn chỉ định trên máy.
mkdir mysite
echo "<h1>Trang cua toi</h1>" > mysite/index.html
docker run --rm -v "$(pwd)/mysite":/usr/share/nginx/html -p 8080:80 nginx:alpine
Mở http://localhost:8080 thấy nội dung từ mysite/index.html trên máy bạn. Sửa file đó trên host rồi tải lại trang — nội dung đổi ngay, không cần build lại image. Liên kết là hai chiều: container cũng ghi ngược ra host được.
Vì tính hai chiều và tức thời này, bind mount rất hợp cho lập trình: mount mã nguồn từ host vào container để vừa code trên host vừa thấy thay đổi trong container. Nó cũng hợp để đưa file cấu hình từ host vào (nginx.conf, file .env...).
Cú pháp
-vvới bind mount cần đường dẫn tuyệt đối ở vế host ($(pwd)/mysite, không phải./mysite). Nếu vế trái không phải đường dẫn tuyệt đối, Docker hiểu nhầm thành named volume.
-v và --mount: hai cú pháp
Docker có hai cách viết. -v ngắn gọn (dùng nhiều trong bài này). --mount dài hơn nhưng rõ ràng, được khuyến nghị cho cấu hình phức tạp:
# tương đương -v demo-data:/data
docker run --mount type=volume,source=demo-data,target=/data alpine ...
# tương đương -v "$(pwd)/mysite":/html
docker run --mount type=bind,source="$(pwd)/mysite",target=/html alpine ...
--mount bắt bạn ghi rõ type=volume hay type=bind, nên tránh được lỗi nhầm volume/bind mount kể trên.
Chọn volume hay bind mount
Tóm theo khuyến nghị của tài liệu:
| Tình huống | Nên dùng |
|---|---|
| Dữ liệu ứng dụng cần bền vững (database, upload) | Volume (Docker quản lý, ưu tiên) |
| Code/file cấu hình từ host, môi trường dev | Bind mount |
| Dữ liệu tạm, không cần giữ, muốn nhanh | tmpfs (--tmpfs) |
Quy tắc thực dụng: trên server production, dữ liệu lâu dài dùng volume. Bind mount để dành cho lúc phát triển và đưa cấu hình vào.
Ví dụ thật: database giữ được dữ liệu
Đây là lý do volume tồn tại. Chạy PostgreSQL với một volume cho thư mục dữ liệu của nó:
docker run -d --name db \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16-alpine
Bạn có thể docker rm -f db rồi chạy lại lệnh trên — database vẫn còn nguyên dữ liệu, vì nó nằm trong volume pgdata, không trong container. Không có volume, mỗi lần xóa container là mất sạch database.
🧹 Dọn dẹp
docker rm -f db 2>/dev/null
docker volume rm demo-data pgdata 2>/dev/null
docker volume prune # dọn volume mồ côi còn lại
rm -rf mysite
Nhớ: xóa volume là xóa dữ liệu thật, nên chỉ prune khi chắc chắn.
Tổng kết
Writable layer của container là tạm thời; dữ liệu cần giữ phải nằm ngoài container. Volume do Docker quản lý, là lựa chọn ưu tiên cho dữ liệu bền vững như database. Bind mount gắn thẳng một thư mục host, hợp cho dev và file cấu hình. Cả hai đều sống độc lập với vòng đời container.
Container của ta giờ chạy được và giữ được dữ liệu. Nhưng các container vẫn cô lập nhau (nhớ network namespace ở Bài 2). Bài 7 cho chúng nói chuyện với nhau và với bên ngoài: mạng trong Docker.