Bài 03
Network & Storage: kết nối container và giữ data
Bài 03 — Network & Storage: kết nối container và giữ data
Bài 01-02 bạn đã tạo được container và image custom. Giờ là lúc giải quyết 2 vấn đề cốt lõi:
- Network — làm sao container giao tiếp với nhau và với thế giới?
- Storage — làm sao giữ data khi container bị xóa?
Nếu bài 02 dạy "đóng gói app", bài này dạy "vận hành stack" — multiple container kết nối + persist data. Sau bài này bạn sẽ dựng được full stack (web + database + cache) trên Docker mà data không mất.
Bài chia 2 phần lớn:
- Phần A — Docker Network
- Phần B — Docker Storage
Phần A — Docker Network
Vấn đề: container giao tiếp thế nào?
3 câu hỏi cần trả lời:
- Container → ngoài internet: container web call API Google được không?
- Ngoài → container: user từ internet truy cập web container như thế nào?
- Container ↔ Container: web container connect đến database container như thế nào?
Mỗi câu trả lời cần 1 loại network khác nhau. Docker cung cấp 5 driver mạng để cover mọi case.
Internet (8.8.8.8)
▲
│ ❓ container ra ngoài?
│
┌──────┴──────────┐
│ Docker Host │
│ │
❓ user vào? │ ┌───────────────┐│
────────────┤►│ Container Web ││
│ └───────┬───────┘│
│ │ │
│ ❓ web ↔ db? │
│ │ │
│ ┌───────▼───────┐│
│ │ Container DB ││
│ └───────────────┘│
└──────────────────┘5 loại Network Driver
┌──────────────────────────────────────────────────────────────┐
│ 5 Docker Network Drivers │
├─────────────┬────────────────────────────────────────────────┤
│ bridge │ Default. Container có IP riêng, NAT ra ngoài. │
│ │ Use case: hầu hết trường hợp. │
├─────────────┼────────────────────────────────────────────────┤
│ host │ Container DÙNG TRỰC TIẾP network host. │
│ │ Use case: high performance, port host = port │
│ │ container. │
├─────────────┼────────────────────────────────────────────────┤
│ none │ Container KHÔNG có network (chỉ loopback). │
│ │ Use case: batch job offline, security extreme. │
├─────────────┼────────────────────────────────────────────────┤
│ overlay │ Multi-host network (Docker Swarm). │
│ │ Use case: container chạy trên nhiều host khác │
│ │ nhau, giao tiếp như cùng mạng. │
├─────────────┼────────────────────────────────────────────────┤
│ macvlan │ Container có IP RIÊNG trên mạng vật lý. │
│ │ Use case: app yêu cầu IP thật, legacy network. │
└─────────────┴────────────────────────────────────────────────┘1. Bridge Network (mặc định)
Khi tạo container mà không chỉ định network, Docker tự động gán vào bridge network mặc định (tên bridge).
Internet
▲
│ NAT (qua iptables)
│
┌────┴─────┐
│ Host eth0│ IP: 192.168.1.100
└────┬─────┘
│
┌────┴─────────────────────────────┐
│ docker0 (bridge interface) │
│ IP: 172.17.0.1 │
└────┬─────────┬─────────┬─────────┘
│ │ │
┌────┴────┐ ┌──┴─────┐ ┌─┴──────┐
│Container│ │Container│ │Container│
│ 172.17. │ │ 172.17. │ │ 172.17. │
│ 0.2 │ │ 0.3 │ │ 0.4 │
│ (nginx) │ │ (mysql) │ │ (redis) │
└─────────┘ └─────────┘ └─────────┘
Mỗi container có IP riêng trong dải 172.17.0.0/16
Ra internet: NAT qua host eth0
User truy cập từ ngoài: cần -p port mappingĐặc điểm:
- Container có IP riêng trong dải 172.17.0.0/16 (subnet bridge default).
- Container talk với nhau qua docker0 bridge interface.
- Ra internet: NAT port qua host.
- Vào từ ngoài: phải
-p HOST:CONTAINERmapping.
⚠️ Nhiều container cùng default bridge không thấy nhau bằng tên (DNS không work). Phải dùng IP — không tin cậy vì IP thay đổi. Xem section "Custom Bridge" dưới.
2. Host Network
Loại bỏ lớp network isolation — container SỬ DỤNG TRỰC TIẾP mạng của host (dùng luôn IP của host).
Internet
▲
│
┌────┴────────┐
│ Host eth0 │ IP: 192.168.1.100
│ │
│ ┌─────────┐ │
│ │Container│ │ ← container dùng TRỰC TIẾP
│ │ nginx │ │ network của host
│ │ port 80 │ │ port container = port host
│ └─────────┘ │
└─────────────┘
Container nginx listen port 80
→ user truy cập http://192.168.1.100 vào container thẳng
→ KHÔNG cần -p 80:80# Run với host network
docker run -d --network host nginx
# Container nginx tự bind port 80 của host
# Không cần -p, không có NATĐặc điểm:
- ✅ Hiệu suất cao — không có NAT overhead.
- ✅ Port container = port host — đơn giản.
- ❌ Dễ xung đột port — chỉ 1 container dùng port 80, container thứ 2 nginx fail vì port đã bị chiếm.
- ❌ Không có isolation — container thấy mọi interface của host.
Use case: app performance-critical (HFT trading, video streaming) hoặc khi cần access raw network của host (network sniffer, monitoring agent).
3. None Network
Container KHÔNG có network — chỉ có loopback interface (127.0.0.1).
┌─────────────────┐
│ Container │
│ ┌────────────┐ │
│ │ lo (127.0 │ │ ← chỉ có loopback
│ │ .0.1) │ │
│ └────────────┘ │
│ │
│ (KHÔNG có eth0)│
│ (Không ra │
│ internet) │
└─────────────────┘docker run --network none alpine ip addr
# 1: lo: <LOOPBACK,UP,LOWER_UP> ...
# inet 127.0.0.1/8 scope host lo
# (không có eth0)Use case:
- Batch job xử lý local data, không cần network.
- Security cực nghiêm ngặt — container chạy code không tin cậy.
- Test isolation.
4. Overlay Network (Docker Swarm)
Dùng để kết nối container chạy trên các host khác nhau trong cụm Docker Swarm.
Host A (Swarm Node) Host B (Swarm Node)
┌─────────────────────┐ ┌─────────────────────┐
│ ┌────────┐ ┌──────┐ │ │ ┌────────┐ ┌──────┐ │
│ │Web-1 │ │DB-1 │ │ │ │Web-2 │ │Cache │ │
│ │10.0.0.2│ │10.0.0│ │ │ │10.0.0.4│ │10.0.0│ │
│ │ │ │.3 │ │ │ │ │ │.5 │ │
│ └────┬───┘ └──┬───┘ │ │ └────┬───┘ └──┬───┘ │
└──────┼────────┼─────┘ └──────┼────────┼─────┘
│ │ │ │
└────────┼──────────────────┼────────┘
│ Overlay (VXLAN) │
│ 10.0.0.0/24 │
└──────────────────────────┘
4 container ở 2 host khác nhau,
nhưng cùng overlay network → giao tiếp như cùng LAN
KHÔNG cần NATĐặc điểm:
- Multi-host networking — container ở host A talk với container ở host B trực tiếp.
- Không cần NAT giữa các host.
- Cần Docker Swarm hoặc Kubernetes mode.
- Built-in encryption (optional).
⚠️ Overlay phức tạp hơn các loại khác. Production thường dùng K8s thay Swarm → cần học K8s networking riêng.
5. Macvlan Network
Cho phép container có địa chỉ IP RIÊNG trên mạng vật lý — giống như 1 máy thực.
Mạng vật lý: 192.168.1.0/24
───────┬───────┬───────┬───────┬────────
│ │ │ │
┌──┴──┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐
│ PC │ │Host │ │Cont1│ │Cont2│
│.10 │ │.100 │ │.101 │ │.102 │ ← container có IP
└─────┘ └─────┘ └─────┘ └─────┘ riêng trên LAN
Router/switch không phân biệt được
container và máy vật lý# Tạo macvlan network
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
my_macvlan
# Container có IP thực trên LAN
docker run --network my_macvlan --ip 192.168.1.101 nginxUse case:
- App yêu cầu IP thật (vd: monitoring agent cần broadcast trên LAN).
- Legacy app không support reverse proxy.
- Tương thích với DHCP/firewall theo IP.
⚠️ Nhiều DC/cloud cấm macvlan (security policy). Production cần check trước.
So sánh nhanh 5 driver
| Driver | Scope | IP | NAT | Port mapping | Use case |
|---|---|---|---|---|---|
| bridge | 1 host | Riêng (172.17.x.x) | ✅ | Cần -p |
Default, most cases |
| host | 1 host | = IP host | ❌ | Không cần -p |
High performance |
| none | 1 host | ❌ | ❌ | N/A | Offline batch |
| overlay | Multi-host | Riêng (10.0.x.x) | ❌ | Service publish | Swarm/K8s |
| macvlan | 1 host | Trên LAN vật lý | ❌ | Truy cập trực tiếp | Legacy, special |
Network Commands
# Liệt kê network
docker network ls
# NETWORK ID NAME DRIVER SCOPE
# abc123 bridge bridge local
# def456 host host local
# ghi789 none null local
# Kiểm tra cấu hình network (xem container nào connect, IP, subnet)
docker network inspect bridge
# Tạo custom network
docker network create my-network
docker network create --driver bridge --subnet 10.5.0.0/16 my-app-net
# Connect container vào network
docker network connect my-network my-container
# Disconnect
docker network disconnect my-network my-container
# Xóa network
docker network rm my-networkDefault Bridge vs Custom Bridge — quan trọng
Đây là điểm nhiều người newbie không biết, gây bug ngu:
Default Bridge (network "bridge"): Custom Bridge (network "my-net"):
┌────────────────────────────┐ ┌────────────────────────────┐
│ - DNS giữa container: │ │ - DNS giữa container: │
│ ❌ KHÔNG work │ │ ✅ Work qua --name │
│ │ │ │
│ - Phải dùng IP để kết nối │ │ - Container ping bằng tên │
│ (IP có thể đổi) │ │ (stable, không đổi) │
│ │ │ │
│ - Container auto join │ │ - Phải explicit join │
│ (docker run không -net) │ │ --network my-net │
└────────────────────────────┘ └────────────────────────────┘Demo
# DEFAULT BRIDGE — DNS không work
docker run -d --name web nginx
docker run --rm alpine ping web -c 2
# ping: bad address 'web' ← FAIL
# CUSTOM BRIDGE — DNS work
docker network create my-net
docker run -d --name web --network my-net nginx
docker run --rm --network my-net alpine ping web -c 2
# 64 bytes from web (172.18.0.2) ← OK→ Production luôn dùng custom bridge. Đừng dùng default bridge cho app thực.
DNS giữa container — magic của Docker
Trong custom bridge network, container talk với nhau qua TÊN (--name), không qua IP.
Custom Network "app-net"
┌────────────────────────────────────┐
│ │
│ ┌──────┐ ┌──────┐ │
│ │ web │────────►│ db │ │
│ │ │ ping db│ │ │
│ └──────┘ └──────┘ │
│ │
│ Docker DNS resolver tự động: │
│ "db" → 172.18.0.3 (IP của container│
│ "db" trong network app-net) │
└────────────────────────────────────┘# Tạo network
docker network create app-net
# Run 2 container trong cùng network
docker run -d --name db --network app-net mysql:8
docker run -d --name web --network app-net nginx
# Trong web, ping db
docker exec web ping db -c 2
# 64 bytes from db (172.18.0.2): icmp_seq=1 ... ← OK→ App config có thể hardcode db (tên container) thay vì IP. Container restart đổi IP, nhưng tên không đổi → app vẫn work.
--network-alias — Load Balancing DNS
Nhiều container có thể chia sẻ 1 alias name → DNS round-robin giữa các container = load balancing built-in.
Custom Network "app-net"
┌────────────────────────────────────┐
│ │
│ Alias: "web" │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │web-1 │ │web-2 │ │web-3 │ │
│ │ │ │ │ │ │ │
│ └──────┘ └──────┘ └──────┘ │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ └───┬───┴───┬───┘ │
│ │ │ │
│ ping web → DNS round-robin │
│ lần 1: web-1, lần 2: web-2, ... │
└────────────────────────────────────┘docker network create app-net
# 3 container web, cùng alias "web"
docker run -d --name web-1 --network app-net --network-alias web nginx
docker run -d --name web-2 --network app-net --network-alias web nginx
docker run -d --name web-3 --network app-net --network-alias web nginx
# Ping "web" — round-robin giữa 3 container
docker run --rm --network app-net alpine sh -c "
for i in 1 2 3 4 5; do
ping -c 1 web | head -1
done
"
# PING web (172.18.0.2): ... ← web-1
# PING web (172.18.0.3): ... ← web-2
# PING web (172.18.0.4): ... ← web-3
# PING web (172.18.0.2): ... ← web-1 (round-robin)→ Load balancing đơn giản chỉ qua DNS. Không cần nginx LB cho internal traffic.
Container kết nối nhiều Network
1 container có thể connect đồng thời nhiều network — pattern phổ biến cho microservices: tách traffic frontend ↔ backend.
┌───────────────────────────────┐
│ Container "web" │
│ (kết nối 2 network) │
└────────┬──────────────┬───────┘
│ │
│ │
┌─────────────┴──┐ ┌────┴──────────┐
│ │ │ │
│ Network │ │ Network │
│ "frontend-net" │ │ "backend-net" │
│ 172.20.0.0/16 │ │ 172.21.0.0/16 │
│ │ │ │
│ ┌─────────────┐ │ │ ┌────────────┐ │
│ │ container │ │ │ │ container │ │
│ │ user-facing │ │ │ │ db, cache │ │
│ │ (lb, api-gw)│ │ │ │ (internal) │ │
│ └─────────────┘ │ │ └────────────┘ │
└─────────────────┘ └────────────────┘
(public-ish) (private)# Tạo 2 network tách biệt
docker network create frontend-net
docker network create backend-net
# Run db chỉ trong backend (private)
docker run -d --name db --network backend-net mysql:8
# Run web kết nối CẢ 2 network
docker run -d --name web --network frontend-net nginx
docker network connect backend-net web # ← thêm network thứ 2
# Run load balancer chỉ trong frontend (public)
docker run -d --name lb --network frontend-net haproxy
# Test:
docker exec web ping db # ✅ OK (cùng backend-net)
docker exec web ping lb # ✅ OK (cùng frontend-net)
docker exec lb ping db # ❌ FAIL (khác network)Pattern này quan trọng:
- DB không expose ra public — chỉ container app (web) thấy.
- Load balancer không thấy DB — security boundary.
- Web đứng giữa 2 network, làm cầu nối.
→ Đây là microservices pattern chuẩn. Sẽ thấy lại trong Kubernetes (NetworkPolicy).
Immutable Infrastructure — không sửa container, deploy lại
Đây là mindset quan trọng nhất của container. Tài liệu của bạn ghi:
"với container không thay đổi chỉ re-deploy"
❌ Mindset cũ (mutable): ✅ Mindset container (immutable):
VM/Server cũ: Container:
┌──────────────┐ ┌──────────────┐
│ App v1.0 │ │ Image v1.0 │ ← deploy
│ │ ──update──► └──────┬───────┘
│ → patch │ │
│ → config sửa│ │ build image mới
│ → log tích │ ▼
│ tụ │ ┌──────────────┐
│ │ │ Image v1.1 │
└──────────────┘ └──────┬───────┘
"Pet" — nuôi │
chăm sóc lâu dài │ docker rm v1.0
│ docker run v1.1
▼
┌──────────────┐
│ Container │
│ v1.1 (mới) │
└──────────────┘
"Cattle" — thay
mới khi cầnQuy tắc immutable:
- Container không bao giờ "update". Code đổi → build image v1.1 → deploy container mới.
- Container không "patch" — security update? Build image mới, deploy.
- Config không sửa trong container — đổi env var hoặc volume → deploy mới.
Lợi ích:
- Reproducible — image v1.1 build đâu cũng giống nhau.
- Rollback dễ — fail thì pull image v1.0, deploy lại.
- No "configuration drift" — server không trở nên "đặc biệt" theo thời gian.
- Disaster recovery — server cháy? Pull image, deploy. Như mới.
→ Quy tắc: container là cattle, not pet. Nuôi đàn, không nuôi con. Cần thì thay con mới.
Phần B — Docker Storage
Vấn đề: container ephemeral
Container là 1 layer trong image. Khi container bị xóa, dữ liệu trong container cũng mất đi.
┌──────────────────────────────────────┐
│ Container (running) │
│ │
│ ┌──────────────────────────┐ │
│ │ Writable Layer │ │
│ │ ├── /var/lib/mysql/ │ │
│ │ │ ├── ibdata1 │ ← 8GB │
│ │ │ ├── mysql.db │ data│
│ │ │ └── ... │ │
│ │ ├── /tmp/ │ │
│ │ └── /home/ │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ Image layers (read-only) │ │
│ └──────────────────────────┘ │
└──────────────────────────────────────┘
│
│ docker rm
▼
┌───────┐
│ 💥 │ ← Writable layer mất
│ GONE │ 8GB data mất sạch
└───────┘⚠️ Container chạy database mà không có persistent storage = catastrophe. Restart container = mất hết data.
→ Cần map data ra ngoài container, bộ nhớ trên host.
Persistent Data — 3 cách
Persistent Data giúp lưu trữ dữ liệu mà không bị mất khi container dừng/xóa.
Docker có 3 phương pháp:
┌──────────────────────────────┐
│ Persistent Storage │
└────────────┬─────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Volume │ │ Bind │ │ tmpfs │
│ │ │ Mount │ │ │
│ Docker │ │ Host │ │ RAM │
│ managed │ │ disk │ │ (tạm) │
└─────────┘ └─────────┘ └─────────┘| Phương pháp | Storage | Quản lý | Tốc độ | Persistent |
|---|---|---|---|---|
| Volume | Host disk (/var/lib/docker/volumes/) |
Docker | Trung bình | ✅ Yes |
| Bind Mount | Host disk (đường dẫn tùy ý) | Bạn | Trung bình | ✅ Yes |
| tmpfs | RAM | Docker | Rất nhanh | ❌ No (mất khi container die) |
1. Volume — Docker quản lý
Volume là phương pháp được Docker quản lý hoàn toàn:
- Docker tạo, sửa, xóa volume.
- Lưu ở 1 đường dẫn cố định:
/var/lib/docker/volumes/<name>/_data. - Host không thấy data trực tiếp (trừ khi vào đúng path Docker).
- Bảo mật và quyền truy cập do Docker kiểm soát.
┌──────────────────────────────────┐
│ Docker Host │
│ │
│ /var/lib/docker/volumes/ │
│ └── mydata/ │
│ └── _data/ ← data thật ở đây
│ ├── file1 │
│ ├── file2 │
│ └── ... │
│ │
│ ┌──────────────────────┐ │
│ │ Container │ │
│ │ │ │
│ │ /app/data ──────────┼─────────┘ ← mount point
│ │ (trong container) │
│ └──────────────────────┘
└────────────────────────────────────┘
Data ghi vào /app/data trong container
→ thực ra ghi vào /var/lib/docker/volumes/mydata/_data trên hostTạo và dùng Volume
# Tạo volume
docker volume create mydata
# Gắn volume vào container
docker run -d \
-v mydata:/data \
--name my-container \
nginx
# -v <volume-name>:<path-trong-container>→ Mọi thứ container ghi vào /data được lưu vào volume mydata. Container die → data còn → tạo container mới mount lại → data nguyên vẹn.
Volume Commands
# Liệt kê volume
docker volume ls
# DRIVER VOLUME NAME
# local mydata
# Xem chi tiết (vị trí trên host, driver, mount point)
docker volume inspect mydata
# [
# {
# "Driver": "local",
# "Mountpoint": "/var/lib/docker/volumes/mydata/_data",
# "Name": "mydata",
# ...
# }
# ]
# Xóa volume
docker volume rm mydata
# Xóa mọi volume không dùng
docker volume pruneCú pháp -v vs --mount
# Cũ: -v (short syntax)
docker run -v mydata:/data nginx
# Mới: --mount (explicit syntax, recommend)
docker run --mount type=volume,source=mydata,target=/data nginx--mount rõ ràng hơn, ít lỗi hơn. Modern Docker khuyến cáo dùng --mount.
⚠️ Docker không hỗ trợ gắn volume vào container đang chạy. Phải stop + recreate container.
Backup & Restore Volume
BACKUP FLOW:
┌─────────────────┐ ┌─────────────────┐
│ Source Volume │ │ Host disk │
│ "mydata" │ │ (pwd) │
│ │ │ │
│ /var/lib/ │ │ ./backup.tar │
│ docker/ │ │ .gz │
│ volumes/ │ │ │
└────────┬────────┘ └────────▲────────┘
│ │
│ mount -v mydata:/data │ mount -v $(pwd):/backup
│ │
└───────┬──────────────────────────┘
▼
┌────────────────────┐
│ Alpine container │
│ (tạm thời) │
│ │
│ tar czf │
│ /backup/file.tar.gz│
│ -C /data . │
│ │
│ Auto delete (--rm) │
└────────────────────┘
RESTORE FLOW: ngược lại
┌─────────────────┐ ┌─────────────────┐
│ New Volume │ │ Host disk │
│ "new_vol" │◄───tar xzf────│ ./backup.tar.gz │
│ (empty) │ │ │
└─────────────────┘ └─────────────────┘
(qua alpine container)# Backup volume "mydata" thành file tar
docker run --rm \
-v mydata:/data \
-v $(pwd):/backup \
alpine \
tar czf /backup/my_backup.tar.gz -C /data .
# File my_backup.tar.gz xuất hiện ở thư mục hiện tại
ls -la my_backup.tar.gz
# Restore vào volume mới
docker volume create new_volume
docker run --rm \
-v new_volume:/data \
-v $(pwd):/backup \
alpine \
tar xzf /backup/my_backup.tar.gz -C /dataTrick: dùng container alpine tạm thời để tar/untar volume. Container chạy xong tự xóa (--rm).
Khi nào dùng Volume
- ✅ Database data (MySQL, PostgreSQL, Redis).
- ✅ Cần persistent, không quan tâm chi tiết file trên host.
- ✅ Chạy trên nhiều môi trường (Linux, Mac, Windows — volume work mọi nơi).
- ✅ Cần Docker quản lý lifecycle.
- ❌ Không dễ chỉnh sửa file từ host (cần
docker cphoặcdocker exec). - ❌ Không trực quan cho develop (sửa code phải build lại).
2. Bind Mount — Host quản lý
Bind Mount là cách gắn 1 thư mục/file CỦA HOST vào container. Host và container chia sẻ data trực tiếp và đồng bộ ngay lập tức.
┌──────────────────────────────────┐
│ Docker Host │
│ │
│ /home/user/myapp/ │
│ ├── index.html │
│ ├── style.css │
│ └── ... │
│ ▲ │
│ │ (cùng 1 file system) │
│ │ │
│ ┌────┴────────────────┐ │
│ │ Container │ │
│ │ │ │
│ │ /usr/share/nginx/ │ │
│ │ html/ │ │
│ └──────────────────────┘ │
└────────────────────────────────────┘
Sửa file trên host → container thấy ngay
Sửa từ container → host thấy ngay
CÙNG file thật, không có copyCú pháp
# Cú pháp -v (cũ)
docker run -v /path/on/host:/path/in/container IMAGE
# Cú pháp --mount (mới, rõ ràng hơn)
docker run --mount type=bind,source=/path/on/host,target=/path/in/container IMAGEVí dụ thực tế
Gắn folder web vào nginx — sửa code không cần rebuild container:
mkdir -p ~/web && cat > ~/web/index.html <<'EOF'
<h1>Hello from bind mount!</h1>
EOF
docker run -d \
--name nginx-dev \
-v ~/web:/usr/share/nginx/html \
-p 8080:80 \
nginx
curl http://localhost:8080
# <h1>Hello from bind mount!</h1>
# Sửa file trên host
echo "<h1>Updated!</h1>" > ~/web/index.html
# Container thấy ngay
curl http://localhost:8080
# <h1>Updated!</h1>→ Không restart container, không rebuild image. Magic cho develop.
Permission issue
⚠️ Bind mount sync 2 chiều → permission có thể không khớp.
Host: user `dev` (UID 1000)
Container: user `nginx` (UID 101)
Container nginx ghi file → file owner UID 101
Host: user dev không đọc/sửa được file đóQuick fix lab (insecure):
sudo chmod -R 777 /home/user/dataProduction: dùng same UID/GID giữa host user và container user, hoặc dùng named volume thay vì bind mount.
Khi nào dùng Bind Mount
- ✅ Development — sửa code, container reflect ngay.
- ✅ Web server — update static file không cần restart.
- ✅ Log files — log container ra ngoài host để dễ đọc/parse.
- ✅ Config files — mount config từ host vào container (nginx.conf, app.config).
- ✅ Backup/restore — copy data từ container ra host.
- ❌ Production database — phụ thuộc filesystem cụ thể, có thể không portable.
- ❌ Permission phức tạp — sync UID/GID giữa host và container.
3. tmpfs — RAM storage
Lưu data vào RAM (bộ nhớ tạm). Cực nhanh nhưng mất khi container die.
┌──────────────────────────────────┐
│ Docker Host │
│ │
│ ┌──────────────────────────┐ │
│ │ Container │ │
│ │ │ │
│ │ /tmp/cache │ │
│ │ │ │ │
│ └─────┼────────────────────┘ │
│ │ │
│ │ (in RAM, không lên disk)│
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ Host RAM (8GB) │ │
│ │ ████████░░░░░░░ 50% │ │
│ └──────────────────────────┘ │
│ │
│ Container die → RAM region freed│
│ │
└────────────────────────────────────┘docker run -d \
--tmpfs /tmp:rw,size=512m \
--name my-app \
nginx
# Container có /tmp 512MB trong RAMUse case:
- Session cache — data tạm cho 1 phiên, không cần persistent.
- Hiệu suất cao — RAM nhanh hơn disk 100-1000x.
- Security — secret/sensitive data không lưu disk.
So sánh 3 phương pháp
| Tiêu chí | Volume | Bind Mount | tmpfs |
|---|---|---|---|
| Vị trí | /var/lib/docker/volumes/ |
Bất kỳ đâu trên host | RAM |
| Quản lý | Docker | Bạn | Docker (auto cleanup) |
| Persistent | ✅ Yes | ✅ Yes | ❌ No |
| Tốc độ | Trung bình | Trung bình | Rất nhanh |
| Truy cập từ host | Khó (cần Docker path) | Trực tiếp | Không |
| Use case | DB data | Dev code, config | Session cache |
| Portability | ✅ Cross-OS | ⚠️ Path-dependent | ✅ |
Quick rule
Production database → Volume
Development code → Bind Mount
Config files → Bind Mount (mount nginx.conf, .env)
Session/cache temporary → tmpfs
Backup/restore → Volume + tarDecision flowchart — chọn nhanh storage method
┌──────────────────────────┐
│ Có cần persistent │
│ (data sống lâu hơn │
│ container)? │
└────────────┬─────────────┘
│
┌────────────┴────────────┐
│ │
NO YES
│ │
▼ ▼
┌──────────────┐ ┌────────────────────┐
│ tmpfs │ │ Cần sửa file từ │
│ (RAM, cache, │ │ host dễ dàng │
│ session) │ │ (dev, log, config)? │
└──────────────┘ └─────────┬──────────┘
│
┌─────────────┴─────────────┐
│ │
YES NO
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ Bind Mount │ │ Volume │
│ │ │ │
│ - dev code │ │ - production DB │
│ - config files │ │ - app state │
│ - log to host │ │ - upload folder │
└─────────────────┘ └──────────────────┘docker cp — copy file giữa host và container
Copy file giữa host và container đang chạy, không cần mount.
# Copy file từ host vào container
docker cp /home/user/data.txt my-container:/app/data.txt
# Copy file từ container ra host
docker cp my-container:/app/output.log /home/user/output.log
# Copy thư mục
docker cp /home/user/data my-container:/app/data
# Wildcard không hỗ trợ — copy folder thay vìUse case:
- Quick file transfer cho debug.
- Copy log ra ngoài để phân tích.
- Đẩy file vào container đang chạy mà không restart.
⚠️ Đây là ad-hoc command, không phải solution persistent. Production dùng volume.
docker commit — tạo image từ container
Tạo image mới với toàn bộ trạng thái của container đang chạy.
# Run container, sửa đổi gì đó
docker run -it --name my-temp ubuntu bash
# Trong container:
apt update && apt install -y curl vim
exit
# Container còn (đã exit), commit thành image
docker commit my-temp my-custom-ubuntu
# Image mới có sẵn curl + vim
docker run --rm my-custom-ubuntu which curl
# /usr/bin/curl⚠️ Anti-pattern trong production. Lý do:
- Image build từ
docker commitkhông reproducible (không có Dockerfile). - Khó audit, khó update.
- Image to (chứa cache, log của container).
→ Chỉ dùng docker commit cho debug/backup tạm. Production luôn build qua Dockerfile.
Câu chuyện thực tế
Năm 2021, mình làm freelance cho 1 startup nhỏ. Họ deploy app lên 1 server VPS, mọi thứ trong Docker. Sau 6 tháng chạy ngon, 1 ngày sếp gọi panic:
"App chạy vẫn được nhưng tất cả data từ tháng trước biến mất."
SSH vào server kiểm tra. Container database mysql đã restart 2 ngày trước (server reboot). Vào container:
docker exec mysql-db mysql -e "SELECT COUNT(*) FROM users"
# COUNT(*): 0 ← !!!!Check lại lệnh deploy ban đầu:
docker run -d --name mysql-db \
-e MYSQL_ROOT_PASSWORD=xxx \
-p 3306:3306 \
mysql:8KHÔNG có -v mount volume. MySQL ghi data vào /var/lib/mysql bên trong container → 6 tháng data nằm trong writable layer.
Khi server reboot → container restart → writable layer mất (Docker tạo writable layer mới).
Họ mất 6 tháng dữ liệu khách hàng vì 1 dòng lệnh thiếu -v.
Fix sau:
docker run -d --name mysql-db \
-e MYSQL_ROOT_PASSWORD=xxx \
-p 3306:3306 \
-v mysql-data:/var/lib/mysql \ # ← Persistent!
mysql:8Plus restore từ backup tape (may còn, may quá).
Bài học:
MỌI container có state PHẢI có volume. DB, redis, app upload folder — không có ngoại lệ.
Test restart container trước production. Stop + remove + recreate → verify data còn nguyên. Đây là smoke test cơ bản.
Backup volume định kỳ. Tar volume ra disk, copy ra S3. Test restore quý/tháng.
docker runkhông có-v= data ephemeral. Recheck mọi lệnh deploy DB trước go live.Đọc docs official. MySQL image
mysql:8Dockerfile cóVOLUME ["/var/lib/mysql"]— nhưng nếu user không mount volume, Docker tạo anonymous volume tự xóa khi container bị--rm. Hint không đủ — phải explicit-v.Server reboot là test thực tế. Reboot host = restart Docker daemon = mọi container recreate. Đây là moment of truth.
Lab đầy đủ
Lab 1: Custom Bridge Network + DNS
# Tạo network
docker network create app-net
# Run web và db cùng network
docker run -d --name db --network app-net mysql:8 \
-e MYSQL_ROOT_PASSWORD=test
docker run -d --name web --network app-net nginx
# Test DNS
docker exec web ping db -c 2
# 64 bytes from db (172.18.0.2) ← DNS work
# Cleanup
docker rm -f web db
docker network rm app-netLab 2: Load balancing qua --network-alias
docker network create lb-net
# 3 container web với cùng alias
for i in 1 2 3; do
docker run -d --name web-$i --network lb-net \
--network-alias web nginx
docker exec web-$i sh -c "echo 'Server $i' > /usr/share/nginx/html/index.html"
done
# Test load balancing
docker run --rm --network lb-net alpine sh -c "
for i in 1 2 3 4 5; do
wget -qO- web | head -1
done
"
# Server 1
# Server 2
# Server 3
# Server 1
# Server 2
docker rm -f web-1 web-2 web-3
docker network rm lb-netLab 3: Volume — Database persistent
# Tạo volume
docker volume create db-data
# Run MySQL với volume
docker run -d --name mydb \
-e MYSQL_ROOT_PASSWORD=secret \
-v db-data:/var/lib/mysql \
mysql:8
sleep 30 # đợi MySQL init
# Tạo database + table + data
docker exec -i mydb mysql -psecret <<EOF
CREATE DATABASE shop;
USE shop;
CREATE TABLE products (id INT, name VARCHAR(50));
INSERT INTO products VALUES (1, 'Apple'), (2, 'Banana');
EOF
# XÓA container hoàn toàn
docker rm -f mydb
# Tạo container mới với CÙNG volume
docker run -d --name mydb-new \
-e MYSQL_ROOT_PASSWORD=secret \
-v db-data:/var/lib/mysql \
mysql:8
sleep 30
# Verify data còn nguyên
docker exec mydb-new mysql -psecret -e "SELECT * FROM shop.products"
# +----+--------+
# | id | name |
# +----+--------+
# | 1 | Apple |
# | 2 | Banana |
# +----+--------+
# Cleanup
docker rm -f mydb-new
docker volume rm db-dataLab 4: Bind Mount — Development workflow
mkdir ~/myweb && echo "<h1>Version 1</h1>" > ~/myweb/index.html
docker run -d --name dev-web \
-v ~/myweb:/usr/share/nginx/html \
-p 8080:80 \
nginx
curl http://localhost:8080
# <h1>Version 1</h1>
# Sửa file trên host
echo "<h1>Version 2 - no restart!</h1>" > ~/myweb/index.html
curl http://localhost:8080
# <h1>Version 2 - no restart!</h1> ← Container reflect ngay!
docker rm -f dev-webLab 5: Volume backup + restore
# Tạo volume có data
docker volume create source-vol
docker run --rm -v source-vol:/data alpine sh -c "
echo 'Important data' > /data/file1.txt
echo 'More data' > /data/file2.txt
"
# Backup volume
docker run --rm \
-v source-vol:/data \
-v $(pwd):/backup \
alpine \
tar czf /backup/backup.tar.gz -C /data .
ls -lh backup.tar.gz
# -rw-r--r-- 1 root root 200 ... backup.tar.gz
# Restore vào volume mới
docker volume create restored-vol
docker run --rm \
-v restored-vol:/data \
-v $(pwd):/backup \
alpine \
tar xzf /backup/backup.tar.gz -C /data
# Verify
docker run --rm -v restored-vol:/data alpine ls /data
# file1.txt
# file2.txt
# Cleanup
docker volume rm source-vol restored-vol
rm backup.tar.gzLab 6: tmpfs trong RAM
docker run -d --name cache-app \
--tmpfs /tmp:rw,size=100m \
nginx
# Ghi file vào tmpfs
docker exec cache-app sh -c "
dd if=/dev/zero of=/tmp/cache bs=1M count=50
df -h /tmp
"
# Filesystem Size Used Avail Use%
# tmpfs 100M 50M 50M 50%
# Stop + start container → tmpfs reset
docker restart cache-app
docker exec cache-app df -h /tmp
# tmpfs 100M 0 100M 0% ← Reset!
docker rm -f cache-appPitfalls
Default bridge — DNS không work → container không thấy nhau bằng tên. Luôn tạo custom bridge cho app.
Run DB không có
-vvolume → restart = mất data. Catastrophic cho production.Bind mount permission sai → container không ghi được file. Quick fix
chmod 777(insecure), tốt hơn: align UID/GID.Host network conflict — 2 container cùng dùng
--network hostlisten cùng port → port busy fail.docker cpthay vì volume → mọi lần update phảicplại. Anti-pattern, dùng bind mount/volume.docker commitcho production → image không reproducible, khó audit. Chỉ dùng debug.Volume name trùng giữa 2 stack → 2 app share data ngẫu nhiên. Đặt tên rõ ràng (
projectA_db_data).Bind mount path tuyệt đối → script portable kém. Dùng
$(pwd)hoặc env var.Quên
docker volume pruneđịnh kỳ → volume orphan tích tụ, ngốn disk. Cleanup tháng/tuần.macvlan trong VPN tunnel → có thể không work do MTU/encapsulation. Test trước.
Overlay không bật ingress firewall → Swarm port (2377, 7946, 4789) bị chặn → cluster fail.
--tmpfssize không set → mặc định no limit, có thể OOM host. Luônsize=Nm.Bind mount vào critical path (
/etc,/lib) → container thấy file host, security risk. Mount đúng folder cần.DB volume mount path sai — MySQL
/var/lib/mysql, PostgreSQL/var/lib/postgresql/data, Mongo/data/db. Check docs image.
Tóm tắt
Network
- 5 network drivers: bridge (default), host, none, overlay (Swarm), macvlan.
- Bridge default: container có IP riêng, NAT ra ngoài, cần
-pcho inbound. DNS không work giữa các container. - Custom bridge: DNS WORK giữa container qua
--name. Production luôn dùng custom bridge. --network-aliascho phép nhiều container share 1 tên → load balancing built-in qua DNS round-robin.- Host network: zero overhead, dễ conflict port.
- None: offline batch, security extreme.
- Overlay: multi-host (Swarm/K8s).
- Macvlan: container có IP thật trên LAN.
- 1 container nhiều network: dùng
docker network connectthêm network → microservices pattern (web cầu nối frontend ↔ backend). - Immutable Infrastructure: không sửa container, deploy lại với image mới. Container là "cattle, not pet".
Storage
- Container ephemeral — writable layer mất khi container rm.
- 3 phương pháp persistent:
- Volume — Docker managed, dùng cho production DB/state.
- Bind Mount — host folder, dùng cho dev code/config.
- tmpfs — RAM, cache tạm cực nhanh, không persistent.
-v name:/pathhoặc--mount type=volume,source=name,target=/path.- Backup volume: dùng container alpine + tar.
docker cp— ad-hoc copy, không thay volume.docker commit— anti-pattern production, chỉ debug.- Quy tắc vàng: mọi container có state PHẢI có volume.
Bài tiếp: 04 — Docker Compose (Capstone). Dựng full LEMP stack qua 1 file YAML, 1 lệnh
docker compose up.