Container có thể bị stop, remove và recreate nhiều lần trong vòng đời triển khai. Nếu dữ liệu ứng dụng chỉ nằm trong writable layer của container, việc xóa container sẽ làm dữ liệu biến mất. Docker volume và bind mount giải quyết vấn đề này bằng cách đưa dữ liệu ra ngoài lifecycle của container, nhưng hai cơ chế có quyền sở hữu, độ phụ thuộc host và rủi ro vận hành khác nhau.
Bài viết này tập trung vào tình huống thực tế: database, uploads, file cấu hình, source code và dữ liệu backup cần đặt ở đâu để không mất sau docker compose up, update image hoặc recreate container. Bạn có thể đọc thêm bài Docker cơ bản cho người mới nếu chưa phân biệt image, container và storage layer.
1. Trả lời nhanh: volume hay bind mount?
Dùng named volume khi dữ liệu do container tạo ra cần tồn tại độc lập với container, chẳng hạn database, queue state hoặc uploads mà Docker quản lý. Dùng bind mount khi bạn cần nhìn thấy file cụ thể trên host, chẳng hạn source code khi development, build artifact hoặc file cấu hình được quản trị ngoài Docker.
| Nhu cầu | Lựa chọn thường phù hợp | Lý do |
|---|---|---|
| Database production | Named volume | Docker quản lý lifecycle, ít phụ thuộc path host |
| Source code development | Bind mount | Sửa file trên host và thấy ngay trong container |
| File config read-only | Bind mount:ro hoặc secret | Cần quản trị file ngoài image |
| Uploads cần backup/migrate | Named volume hoặc bind mount có runbook | Phải có đường backup và restore rõ |
| Dữ liệu tạm | tmpfs hoặc writable layer | Không cần giữ sau recreate |
Container là nơi chạy process; volume hoặc bind mount mới là nơi bạn quyết định dữ liệu sẽ sống sau khi process bị thay thế.Nguyên tắc storage cần nhớ khi vận hành Docker.
2. Writable layer của container có gì nguy hiểm?
Mỗi container có writable layer riêng nằm trên image layer. Ứng dụng ghi file vào đó có thể đọc lại khi container đang tồn tại, nhưng dữ liệu không nên được coi là persistent. Khi container bị xóa và tạo lại từ image, writable layer cũ cũng bị xóa theo.
# Không nên lưu database chỉ trong container
docker run -d --name db postgres:16
# Xóa container; dữ liệu trong writable layer không còn
docker rm -f db
# Cách đúng: mount named volume
docker run -d --name db --mount source=app_db,target=/var/lib/postgresql/data postgres:16Thao tác docker restart không tạo container mới nên thường không xóa writable layer, nhưng deploy thực tế thường có pull image, recreate hoặc đổi config. Vì vậy hãy thiết kế storage theo trường hợp container bị xóa hoàn toàn, không chỉ theo lần restart bình thường.
3. Named volume là gì và dữ liệu được quản lý ra sao?
Volume là data store persistent do Docker tạo và quản lý. Volume tồn tại ngoài lifecycle của một container; xóa container không tự động xóa volume đang được dùng hoặc volume đã tạo. Docker Docs xem volume là cơ chế ưu tiên cho dữ liệu được container tạo và sử dụng.
# Tạo, xem và kiểm tra named volume
docker volume create app_db
docker volume ls
docker volume inspect app_db
# Gắn volume vào container
docker run -d --name db --mount type=volume,src=app_db,dst=/var/lib/postgresql/data postgres:16Volume có thể được dùng lại sau khi recreate container và có thể tham chiếu từ Compose. Tuy nhiên, persistent không đồng nghĩa với backup. Nếu disk host hỏng, volume local cũng có thể mất; bạn vẫn cần backup ra vị trí khác và thử restore định kỳ.
4. Bind mount là gì và khi nào nên dùng?
Bind mount gắn một file hoặc thư mục cụ thể trên host vào đường dẫn trong container. Cách này phù hợp khi host phải nhìn thấy file, ví dụ project source, build output, certificate hoặc file cấu hình. Đổi lại, container bị phụ thuộc vào cấu trúc thư mục, quyền filesystem và hệ điều hành của host.
# Mount source code read-only vào container
docker run --rm -it --mount type=bind,src="$PWD",dst=/workspace,readonly node:22 bashBind mount mặc định có quyền ghi. Một process trong container có thể sửa hoặc xóa file trên host nếu UID/GID và quyền cho phép. Với cấu hình hoặc source chỉ cần đọc, thêm readonly hoặc ro. Với dữ liệu cần ghi, hãy giới hạn path và quyền thay vì mount cả thư mục gốc của host.
5. Khác nhau quan trọng giữa volume và bind mount
| Tiêu chí | Named volume | Bind mount |
|---|---|---|
| Nơi lưu | Docker quản lý trong storage directory | Path cụ thể trên host |
| Phụ thuộc host path | Thấp hơn | Cao |
| Dễ backup/migrate | Có thể quản lý qua Docker CLI/API | Dùng công cụ filesystem của host |
| Truy cập trực tiếp từ host | Không thuận tiện bằng bind mount | Rất thuận tiện |
| Quyền mặc định | Theo mount và process trong container | Thường read-write vào host |
| Use case điển hình | Database, persistent app data | Source, config, artifact |
Không có quy tắc “volume luôn tốt hơn”. Tiêu chí đúng là dữ liệu có cần host truy cập trực tiếp hay không, có chấp nhận phụ thuộc path host không và đội vận hành backup bằng cách nào. Một hệ thống có thể dùng volume cho database, bind mount read-only cho config và secret cho credential cùng lúc.
6. Volume và bind mount có thể che khuất dữ liệu có sẵn
Nếu mount volume hoặc bind mount vào thư mục đã có file trong image, nội dung cũ có thể bị che khuất cho đến khi bỏ mount. Với empty volume, Docker có thể copy dữ liệu có sẵn ở destination vào volume theo mặc định; nếu không muốn copy, cần dùng tuỳ chọn volume-nocopy.
# Kiểm tra destination trước khi mount
docker run --rm image:tag ls -la /app/data
# Ngăn copy dữ liệu pre-existing vào empty volume
docker run -d --mount type=volume,src=app_data,dst=/app/data,volume-nocopy image:tagVới bind mount, directory host sẽ che khuất nội dung trong container. Nếu đường dẫn host không tồn tại, cú pháp -v có thể tạo directory ngoài ý muốn; cú pháp --mount rõ ràng hơn và thường báo lỗi khi source path không tồn tại. Hãy tạo, kiểm tra và phân quyền source trước khi start.
7. Khai báo volume trong Docker Compose
services:
app:
image: ghcr.io/example/app:1.4.0
volumes:
- app_uploads:/var/lib/app/uploads
db:
image: postgres:16
volumes:
- app_db:/var/lib/postgresql/data
volumes:
app_uploads:
app_db:Khi chạy docker compose up lần đầu, Compose tạo volume; những lần chạy sau sẽ dùng lại volume của project. Nếu volume đã được tạo bên ngoài Compose, khai báo external: true để Compose không cố tạo volume mới với tên project khác.
volumes:
shared_uploads:
external: true
name: shared_uploadsĐặt tên volume có chủ ý giúp tránh nhầm giữa staging và production. Bạn có thể dùng project name riêng hoặc tên volume explicit, nhưng phải ghi vào runbook để người khác biết volume nào chứa dữ liệu thật.
8. Bind mount trong Compose: dùng path tuyệt đối và read-only khi có thể
services:
web:
image: nginx:alpine
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./public:/usr/share/nginx/html:ro
- ./var/log/nginx:/var/log/nginx
app:
build: .
volumes:
- type: bind
source: ./config/production.yaml
target: /app/config/production.yaml
read_only: trueRelative path thường được tính theo vị trí file Compose. Trên server, hãy xác nhận working directory, owner, mode và SELinux policy nếu có. Tránh bind mount /, /var/run/docker.sock hoặc thư mục chứa toàn bộ home user chỉ để “cho nhanh”; đây là quyền rất lớn và có thể biến lỗi ứng dụng thành sự cố host.
9. Recreate container mà không mất database
- Xác định container đang dùng mount nào bằng
docker inspect. - Kiểm tra volume name và dung lượng trước khi thay image hoặc Compose file.
- Tạo backup database logic và snapshot/file backup theo chính sách.
- Recreate service mà không dùng cờ xóa volume nếu chưa có chủ đích.
- Kiểm tra log, migration, schema, row count và một thao tác đọc/ghi sau deploy.
- Chỉ prune volume sau khi đã đối chiếu danh sách volume không còn dùng.
# Xem mount của service
docker inspect db --format '{{json .Mounts}}'
# Recreate service nhưng giữ volume
docker compose up -d --force-recreate db
# CẢNH BÁO: không dùng nếu chưa muốn xóa volume
docker compose down -vLệnh docker compose down -v có thể xóa named volumes được khai báo trong Compose. Đây là một trong những nguyên nhân dễ gây mất database nhất khi copy lệnh từ tutorial. Với production, hãy yêu cầu review hoặc dùng runbook có bước xác nhận trước các lệnh destructive.
10. Backup và restore volume đúng cách
Backup volume không nên chỉ là “volume vẫn còn nên yên tâm”. Bạn cần có bản sao bên ngoài host và kiểm tra restore. Một phương án phổ biến là chạy container tạm, mount volume nguồn và bind mount thư mục backup trên host, sau đó tạo archive. Khi restore, giải nén vào volume đích và kiểm tra owner, permission, checksum cùng tính nhất quán ứng dụng.
# Backup named volume ra thư mục hiện tại
docker run --rm --mount source=app_uploads,dst=/source,readonly --mount type=bind,src="$PWD/backups",dst=/backup alpine tar czf /backup/app_uploads-$(date +%F).tar.gz -C /source .
# Kiểm tra archive
tar tzf backups/app_uploads-2026-09-03.tar.gz | headVới database, archive file system không phải lúc nào cũng thay thế được backup logic như pg_dump hoặc mysqldump. Hãy chọn phương án phù hợp với database engine, consistency requirement, RPO/RTO và kích thước dữ liệu. File backup cũng cần retention, mã hóa khi cần và copy sang storage khác.
11. Kiểm tra quyền, owner và UID/GID
Container thường chạy bằng user có UID/GID khác host. Bind mount có thể khiến app không ghi được hoặc ngược lại, ghi thành root vào host. Khi gặp lỗi permission, đừng giải quyết bằng chmod -R 777. Hãy xác định process user, owner thư mục, umask và đường dẫn thực sự cần ghi.
# Xem user của container
docker inspect app --format '{{.Config.User}}'
# Kiểm tra quyền ở host
stat -c '%A %a %U:%G %n' ./var ./var/log
# Kiểm tra mount và quyền bên trong container
docker compose exec app id
docker compose exec app sh -c 'ls -ld /app /app/data'Với volume mới, có thể cần init script hoặc một bước chown có kiểm soát. Với bind mount read-only, lỗi ghi là kỳ vọng và nên được xử lý bằng cấu hình app ghi vào thư mục persistent khác.
12. Những lỗi khiến dữ liệu “tự nhiên” biến mất
- Ghi dữ liệu vào path chưa mount: container chạy bình thường nhưng dữ liệu nằm trong writable layer.
- Đổi tên volume: Compose tạo volume mới, app nhìn thấy thư mục rỗng và tưởng dữ liệu mất.
- Dùng anonymous volume: mỗi container mới có thể nhận volume tên khác nếu không quản lý explicit.
- Chạy down -v hoặc volume prune: lệnh dọn dẹp đã xóa storage chưa đối chiếu.
- Mount nhầm destination: volume rỗng che khuất file seed trong image.
- Backup cùng host: host hỏng thì cả volume và bản backup local có thể mất.
- Không kiểm tra restore: có file archive nhưng không biết dữ liệu có dùng được.
13. Checklist storage cho Compose production
- Liệt kê mọi dữ liệu cần tồn tại sau recreate: database, uploads, media, config, cache và backup.
- Chọn named volume cho data container-managed; chọn bind mount khi host cần truy cập trực tiếp.
- Đặt tên volume explicit và tách staging/production để không mount nhầm.
- Dùng read-only cho config, source hoặc artifact chỉ cần đọc.
- Kiểm tra mọi mount bằng
docker inspecttrước deploy. - Backup ra host khác hoặc object storage, có retention và encryption phù hợp.
- Test restore trên môi trường tạm, không chỉ kiểm tra file archive tồn tại.
- Không chạy
down -vhoặcvolume prunenếu chưa đối chiếu dữ liệu.
14. Kết luận
Recreate container không đáng sợ nếu dữ liệu đã được tách khỏi container và có backup thật. Named volume phù hợp cho database và persistent data do Docker quản lý; bind mount phù hợp cho source, artifact hoặc config cần nhìn thấy trên host, nhưng cần kiểm soát quyền và sự phụ thuộc filesystem.
Điều quan trọng nhất không phải nhớ một câu lệnh mount, mà là biết dữ liệu đang nằm ở đâu, ai có quyền ghi, container nào đang dùng nó, lệnh nào có thể xóa nó và bạn sẽ restore bằng cách nào. Hãy biến các câu trả lời này thành runbook trước khi đưa stack Docker lên production.