Phân tích
7 phút đọc15 tháng 6, 2026245

Docker Volumes & Networking: Hiểu đúng để không mất data oan

Deep dive vào cách Docker xử lý storage và networking từ named volumes, bind mounts đến container DNS resolution.

N

Nguyễn Nhật Long

@nguyennhatlong1303

Docker Volumes & Networking: Hiểu đúng để không mất data oan

Nếu bạn đã đọc bài trước về Docker Compose, chắc bạn đã thấy mình dùng volumes:networks: trong file compose mà không giải thích nhiều. Bài này mình sẽ đào sâu hơn tại sao lại có các loại storage khác nhau, khi nào dùng cái gì, và networking trong Docker thực ra hoạt động như thế nào.

Mình đã từng mất data của một PostgreSQL container chỉ vì không hiểu sự khác biệt giữa named volume và bind mount. Bài học đau lòng, nhưng đáng để chia sẻ.

Ba cách Docker lưu data và chúng không giống nhau

Nhiều người mới học Docker hay nhầm lẫn giữa các loại storage. Thực ra Docker có ba cơ chế hoàn toàn khác nhau, mỗi cái sinh ra để giải quyết một bài toán cụ thể.

LoạiQuản lý bởiData persist?Phụ thuộc host?Dùng khi nào
Named VolumeDocker✅ Có❌ KhôngDatabase, file uploads, production data
Bind MountBạn (host path)✅ Có✅ CóLocal development, live reload
tmpfs MountMemory❌ Mất khi stop❌ KhôngSensitive data tạm thời, cache

Named Volumes cái bạn nên dùng cho production

Named volume là Docker tự quản lý storage, bạn chỉ cần đặt tên:

Terminal
1# Tạo volume
2docker volume create mydata
3
4# Gắn vào container
5docker run -v mydata:/data myapp

Cái hay ở đây là data được lưu ở một chỗ do Docker quản lý (thường ở /var/lib/docker/volumes/ trên Linux), hoàn toàn độc lập với container lifecycle. Bạn stop container, xóa container, data vẫn còn nguyên. Start container mới với cùng volume name data quay lại đầy đủ.

Theo kinh nghiệm của mình, named volume là lựa chọn mặc định cho bất cứ thứ gì cần persist trong production: PostgreSQL, MySQL, Redis, file uploads của user, v.v.

Bind Mounts dev tool số một

Terminal
1docker run -v /host/path:/container/path myapp
2# hoặc dùng absolute path
3docker run -v $(pwd)/src:/app/src myapp

Bind mount map trực tiếp một thư mục trên máy host vào trong container. Bạn edit file trên máy, container thấy ngay lập tức đây là lý do tại sao dev workflow với Docker thường dùng bind mount cho source code.

Nhưng anh em lưu ý: bind mount phụ thuộc vào cấu trúc filesystem của host. File /home/user/project trên máy mình không tồn tại trên máy người khác. Đây là lý do tại sao bind mount không phù hợp cho production.

Một gotcha nữa trên macOS/Windows: bind mount chậm hơn Linux đáng kể vì có thêm lớp virtualization ở giữa. Nếu bạn thấy container chạy chậm bất thường khi dev, đây có thể là nguyên nhân.

tmpfs khi bạn không muốn data tồn tại

Terminal
1docker run --tmpfs /tmp myapp

tmpfs mount chỉ tồn tại trong memory. Container stop là data bay. Nghe có vẻ vô dụng nhưng thực ra rất hữu ích cho:

  • Temporary files cần I/O nhanh
  • Sensitive data như session tokens mà bạn không muốn ghi ra disk
  • Cache trong quá trình build

Quản lý volumes không phải chỉ là tạo và dùng

Một thói quen mình thấy nhiều người bỏ qua là quản lý vòng đời của volumes. Sau một thời gian chạy nhiều container, bạn sẽ có đống volumes orphaned không ai dùng, chiếm disk space.

Terminal
1# Xem tất cả volumes
2docker volume ls
3
4# Inspect chi tiết một volume
5docker volume inspect mydata
6
7# Xóa volume cụ thể
8docker volume rm mydata
9
10# Dọn dẹp tất cả unused volumes cẩn thận!
11docker volume prune

docker volume prune là lệnh mình hay chạy định kỳ trên máy dev, nhưng tuyệt đối không chạy trên production nếu không chắc chắn 100% về những gì đang xảy ra.

Backup và restore volume đừng bỏ qua bước này

Đây là phần nhiều người không nghĩ đến cho đến khi cần. Backup một named volume:

Terminal
1# Backup
2docker run --rm \
3 -v mydata:/data \
4 -v $(pwd):/backup \
5 alpine tar czf /backup/backup.tar.gz /data

Lệnh này spin up một container Alpine tạm thời, mount volume cần backup vào /data, mount thư mục hiện tại vào /backup, rồi tar toàn bộ lại. Kết quả là file backup.tar.gz ngay trên máy host của bạn.

Restore cũng tương tự:

Terminal
1# Restore
2docker run --rm \
3 -v mydata:/data \
4 -v $(pwd):/backup \
5 alpine tar xzf /backup/backup.tar.gz -C /

Mình thấy cái pattern này hay ở chỗ không cần tool gì đặc biệt chỉ cần Alpine và tar là xong. Đơn giản, portable, và hoạt động trên mọi môi trường.

Networking trong Docker không phức tạp như bạn nghĩ

Phần này nhiều người hay bị confused, nhưng thực ra logic khá rõ ràng một khi bạn hiểu được mô hình.

Docker có ba network driver chính:

DriverScopeDùng khi nàoLưu ý
BridgeSingle hostDefault, local dev, production đơn giảnCần port mapping để expose ra ngoài
HostSingle hostKhi cần performance tối đa, không muốn NATChỉ Linux, không dùng được trên macOS/Windows
OverlayMulti-hostDocker Swarm, KubernetesCần orchestration setup

Bridge network và DNS resolution tự động

Khi bạn tạo một custom bridge network và chạy containers trong đó, Docker tự động setup DNS cho bạn:

Terminal
1# Tạo network
2docker network create mynet
3
4# Chạy containers trong cùng network
5docker run -d --network mynet --name api myapi
6docker run -d --network mynet --name db postgres

Bây giờ container api có thể gọi container db bằng hostname db không cần biết IP, không cần hardcode gì cả. Docker DNS resolver tự handle.

Terminal
1# Từ bên trong container api
2curl http://api:3000
3psql -h db -U postgres

Anh em lưu ý: DNS resolution này chỉ hoạt động với custom networks, không phải default bridge network. Đây là lý do tại sao trong Docker Compose, mọi service tự nhiên gọi được nhau bằng service name Compose tự tạo một custom network cho toàn bộ stack.

Host network khi nào thực sự cần?

Terminal
1docker run --network host myapp

Với host network, container share luôn network stack với host. Không có NAT, không cần port mapping, performance tốt nhất. Nhưng mình hiếm khi dùng cái này trong thực tế vì:

  1. Chỉ chạy được trên Linux (macOS và Windows Docker chạy qua VM nên không có tác dụng)
  2. Container có thể bind bất kỳ port nào trên host security risk nếu không cẩn thận
  3. Mất đi tính isolation một trong những lý do chính để dùng Docker

Use case thực tế mình thấy phù hợp nhất là monitoring agents cần truy cập network metrics của host, hoặc các tool như tcpdump cần sniff traffic.

Thực hành: PostgreSQL với persistent storage

Gộp lại tất cả những gì vừa nói, đây là cách setup một PostgreSQL container đúng cách:

Terminal
1# Tạo network và volume
2docker network create pgnet
3docker volume create pgdata
4
5# Chạy PostgreSQL
6docker run -d \
7 --name postgres \
8 --network pgnet \
9 -v pgdata:/var/lib/postgresql/data \
10 -e POSTGRES_PASSWORD=secret \
11 -e POSTGRES_DB=myapp \
12 postgres:15
13
14# Chạy app kết nối đến DB
15docker run -d \
16 --name myapp \
17 --network pgnet \
18 -e DATABASE_URL=postgresql://postgres:secret@postgres/myapp \
19 -p 3000:3000 \
20 myapp:latest

Bây giờ thử test persistence:

Terminal
1# Stop và xóa container postgres
2docker stop postgres && docker rm postgres
3
4# Start lại với cùng volume
5docker run -d \
6 --name postgres \
7 --network pgnet \
8 -v pgdata:/var/lib/postgresql/data \
9 -e POSTGRES_PASSWORD=secret \
10 postgres:15
11
12# Data vẫn còn nguyên

Cái này mình hay demo cho junior dev mới join team nhìn thấy data survive qua container restart là họ hiểu ngay tại sao volumes quan trọng.

Nếu bạn dùng Docker Compose (và bạn nên dùng cho local dev), tất cả những thứ trên được express gọn hơn nhiều:

YAML
1services:
2 db:
3 image: postgres:15
4 volumes:
5 - pgdata:/var/lib/postgresql/data
6 environment:
7 POSTGRES_PASSWORD: secret
8 POSTGRES_DB: myapp
9
10 app:
11 build: .
12 ports:
13 - "3000:3000"
14 environment:
15 DATABASE_URL: postgresql://postgres:secret@db/myapp
16 depends_on:
17 - db
18
19volumes:
20 pgdata:

Compose tự tạo network, tự resolve db hostname, tự manage volume lifecycle. Sạch và readable hơn nhiều so với đống lệnh docker run.

Bài tiếp theo mình sẽ nói về multi-stage builds và tối ưu Docker image size một topic mà theo kinh nghiệm của mình, phần lớn team bỏ qua và rồi tự hỏi tại sao image production lại nặng cả GB.

NN

Nguyễn Nhật Long

@nguyennhatlong1303

Nguyễn Nhật Long is a Senior Frontend Engineer and Frontend Team Leader with 7 years of experience building real-time fintech platforms. Specializing in React, Next.js, TypeScript, and React Native, shipping 10+ products across Web, Mobile, Telegram Mini-Apps, and Web3.

Thấy hay? Chia sẻ cho bạn bè!

Docker Volumes & Networking: Hiểu đúng để không mất data oan — Stacklog