Docker Networking & Volumes: Deep Dive

Bạn đã biết build và chạy container — giờ hãy tìm hiểu cách chúng giao tiếp với nhau và persist data. Networking và storage là hai lĩnh vực developer hay gặp behavior không ngờ tới, và hiểu sâu về chúng sẽ giúp bạn tiết kiệm hàng giờ debug.
Bài này cover mọi thứ bạn cần biết về Docker networking và storage cho production.
Time commitment: 3–4 ngày, 1–2 tiếng mỗi ngày
Prerequisites: Phase 1: Docker Fundamentals, Phase 2: Docker Compose & Multi-Container Apps
Phần 1: Docker Networking
Docker Networking Hoạt Động Như Thế Nào
Khi Docker khởi động, nó tạo một virtual network interface trên host machine. Mỗi container có network namespace riêng — một isolated view của network stack. Các container giao tiếp qua Docker-managed networks.
# Xem tất cả networks trên hệ thống
docker network ls
# Output
NETWORK ID NAME DRIVER SCOPE
a1b2c3d4e5f6 bridge bridge local
b2c3d4e5f6a1 host host local
c3d4e5f6a1b2 none null localDocker có ba built-in networks và cho phép bạn tạo custom networks.
Các Network Driver
Bridge Network (Mặc định)
Bridge driver là default cho standalone containers. Docker tạo một virtual Ethernet bridge (docker0) trên host, mỗi container kết nối vào đó sẽ nhận một IP trong subnet đó.
# Chạy container — tự động join default bridge
docker run -d --name web nginx
# Inspect default bridge network
docker network inspect bridgeGiới hạn của default bridge:
- Containers chỉ giao tiếp được qua IP (không có DNS)
- Không khuyến nghị cho production — dùng custom bridge networks
Custom bridge networks giải quyết vấn đề này:
# Tạo custom network
docker network create myapp-network
# Chạy containers trên đó
docker run -d --name backend --network myapp-network myapp:latest
docker run -d --name db --network myapp-network postgres:16
# backend có thể reach db bằng hostname
docker exec backend ping db # ✅ Hoạt động qua DNSCustom bridge networks cho bạn:
- Tự động DNS resolution theo container name
- Isolation tốt hơn với các container khác
- Có thể connect/disconnect containers lúc runtime
Host Network
Container dùng chung network stack của host — không có network namespace isolation.
docker run --network host nginx
# nginx giờ listen trên host port 80 trực tiếp (không cần -p)Khi nào dùng host networking:
- Cần network performance tối đa (không có NAT overhead)
- Tools cần truy cập host network interfaces (monitoring agents, packet sniffers)
- Chỉ Linux — không hoạt động trên Docker Desktop cho Mac/Windows
Lưu ý bảo mật: Container thấy tất cả host network interfaces. Dùng cẩn thận.
None Network
Tắt hoàn toàn networking cho container.
docker run --network none alpine sh
# Không có network interfaces ngoài loopbackHữu ích cho batch jobs xử lý local files và không cần network.
Overlay Network
Overlay networks trải dài qua nhiều Docker hosts (dùng với Docker Swarm hoặc Kubernetes). Cho phép containers trên các máy khác nhau giao tiếp như trên cùng một network.
# Chỉ có trong Swarm mode
docker network create --driver overlay my-overlayVới single-host development, bạn không cần overlay networks — đó là territory của Kubernetes.
Container DNS và Service Discovery
Docker chạy embedded DNS server tại 127.0.0.11 cho custom networks. Khi container lookup DNS tên của container khác, Docker resolve thành IP của container đó.
# Tạo network và containers
docker network create backend-net
docker run -d --name postgres --network backend-net postgres:16
docker run -d --name redis --network backend-net redis:7
docker run -d --name api --network backend-net myapi:latest
# api có thể reach postgres và redis bằng tên
# postgres://postgres:5432 ✅
# redis://redis:6379 ✅DNS aliases — đặt nhiều hostname cho một container:
docker run -d \
--network backend-net \
--network-alias db \
--network-alias database \
--name postgres \
postgres:16
# Giờ có thể reach bằng "postgres", "db", hoặc "database"Service discovery trong Docker Compose là tự động — mỗi service name trở thành DNS hostname:
services:
api:
build: .
# Có thể reach "db" và "cache" bằng tên
db:
image: postgres:16
cache:
image: redis:7Port Publishing
Containers mặc định bị isolate — bạn phải explicitly publish ports để chúng accessible từ host.
# -p host_port:container_port
docker run -p 8080:80 nginx # host:8080 → container:80
docker run -p 127.0.0.1:8080:80 nginx # chỉ bind vào localhost (an toàn hơn)
docker run -p 80 nginx # random host port → container:80
# Kiểm tra port được assign
docker port <container_id>Best practice: Bind vào 127.0.0.1 trong development để port không bị expose ra local network:
docker run -p 127.0.0.1:5432:5432 postgres:16
# Chỉ accessible từ localhost, không phải các máy khác trong networkNetwork Security và Isolation
Tách networks theo từng tầng
# docker-compose.yml — network isolation kiểu production
services:
nginx:
image: nginx:alpine
networks:
- frontend
- backend
api:
build: .
networks:
- backend
- data
db:
image: postgres:16
networks:
- data # db KHÔNG có trên frontend — nginx không reach được trực tiếp
networks:
frontend:
backend:
data:Architecture này có nghĩa:
nginxnói chuyện vớiapi(cả hai trênbackend)apinói chuyện vớidb(cả hai trêndata)nginxkhông thể reach trực tiếpdb— phải đi quaapi
Internal networks (không có external access)
docker network create --internal secure-net
# Containers trong network này không thể ra internetHữu ích cho databases và internal services không nên có outbound internet access.
Các Lệnh Network Hữu Ích
# List networks
docker network ls
# Inspect một network (hiện containers đang kết nối và IP của chúng)
docker network inspect myapp-network
# Tạo network
docker network create --driver bridge --subnet 172.20.0.0/16 myapp-network
# Connect running container vào network
docker network connect myapp-network my-container
# Disconnect
docker network disconnect myapp-network my-container
# Xóa các networks không dùng
docker network prune
# Xem network settings của container
docker inspect --format='{{json .NetworkSettings}}' my-container | jqPhần 2: Docker Storage
Vấn Đề Với Container Filesystem
Containers có một writable layer nằm trên các image layers. Mặc định, mọi thứ được ghi vào container đều lưu trong writable layer này:
┌─────────────────────────────┐
│ Writable Container Layer │ ← bị xóa khi container bị remove
├─────────────────────────────┤
│ Image Layer 3 (RO) │
├─────────────────────────────┤
│ Image Layer 2 (RO) │
├─────────────────────────────┤
│ Image Layer 1 (RO) │
└─────────────────────────────┘Vấn đề với writable layer:
- Data bị mất khi container bị remove
- Data không thể share giữa các containers
- Ghi dữ liệu dùng copy-on-write storage driver — chậm hơn native filesystem I/O
Docker có ba giải pháp: volumes, bind mounts, và tmpfs.
Volumes
Volumes là cơ chế được ưu tiên để persist data. Docker quản lý hoàn toàn — chúng sống trong /var/lib/docker/volumes/ trên Linux và độc lập với vòng đời container.
# Tạo named volume
docker volume create mydata
# Dùng khi chạy container
docker run -d \
--name postgres \
-v mydata:/var/lib/postgresql/data \
postgres:16
# Data trong /var/lib/postgresql/data persist dù container bị removeVolume trong Docker Compose:
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: secret
volumes:
pgdata: # Docker quản lý volume nàyCác lệnh volume
# List volumes
docker volume ls
# Inspect volume (hiện mountpoint trên host)
docker volume inspect mydata
# Xóa volume
docker volume rm mydata
# Xóa tất cả volumes không dùng
docker volume prune
# Tạo với options
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/path/to/dir \
nfs-volumeChia sẻ volumes giữa các containers
# Container đầu tiên ghi data
docker run -d --name writer -v shared-data:/data alpine \
sh -c "echo 'hello' > /data/message.txt && sleep 3600"
# Container thứ hai đọc từ cùng volume
docker run --rm -v shared-data:/data alpine cat /data/message.txt
# Output: helloBind Mounts
Bind mounts map một đường dẫn trên host filesystem trực tiếp vào container. Không như volumes, bạn kiểm soát chính xác data nằm ở đâu trên host.
# Mount thư mục hiện tại vào container
docker run -v $(pwd):/app node:20 npm test
# Mount một đường dẫn cụ thể trên host
docker run -v /host/path:/container/path nginxBind mount trong Docker Compose (setup dev điển hình):
services:
api:
build: .
volumes:
- ./src:/app/src # source code — live reload
- ./config:/app/config # config files
- /app/node_modules # anonymous volume — không overwrite node_modulesTrick anonymous volume (/app/node_modules) ngăn node_modules của host ghi đè các packages đã install trong container.
Bind mount options
# Read-only mount — container không thể sửa files
docker run -v $(pwd)/config:/app/config:ro myappSo sánh Volumes vs Bind Mounts
| Volumes | Bind Mounts | |
|---|---|---|
| Quản lý bởi | Docker | Bạn (host path) |
| Tốt nhất cho | Persistent data (databases) | Development (live code reload) |
| Portable | ✅ Có | ❌ Phụ thuộc host path |
| Performance | ✅ Tốt hơn trên Linux | ✅ Tốt cho read-heavy |
| Backup | Qua docker volume commands | Truy cập trực tiếp filesystem |
| Production | ✅ Ưu tiên | ⚠️ Dùng cẩn thận |
tmpfs Mounts
tmpfs mounts lưu data trong memory của host — không bao giờ ghi ra disk. Hoàn hảo cho sensitive data (tokens, passwords) không nên persist.
docker run -d \
--tmpfs /tmp \
--tmpfs /run:rw,noexec,nosuid,size=100m \
nginxTrong Docker Compose:
services:
api:
image: myapi
tmpfs:
- /tmp
- /runUse cases:
- Temporary session data
- Sensitive credentials không được ghi ra disk
- High-speed scratch space để xử lý
Storage Drivers
Docker dùng union filesystem (storage driver) để layer images. Driver ảnh hưởng đến I/O performance và thường trong suốt với bạn, nhưng nên biết.
| Driver | OS | Ghi chú |
|---|---|---|
overlay2 | Linux | Mặc định, được khuyến nghị |
aufs | Ubuntu (legacy) | Cũ hơn, đang loại bỏ |
devicemapper | RHEL/CentOS (legacy) | Tránh dùng trừ khi bắt buộc |
vfs | Bất kỳ | Chậm, dùng để test |
# Kiểm tra storage driver của bạn
docker info | grep "Storage Driver"
# Storage Driver: overlay2Storage driver chỉ ảnh hưởng writable container layer — volumes bypass nó hoàn toàn và dùng native filesystem I/O.
Production Storage Patterns
Pattern 1: Database với named volume
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
secrets:
- db_password
volumes:
pgdata:
secrets:
db_password:
file: ./secrets/db_password.txtPattern 2: Application với volumes riêng cho code và data
services:
app:
image: myapp:${VERSION}
volumes:
- uploads:/app/uploads # user-uploaded files persist
- logs:/app/logs # logs persist để debug
tmpfs:
- /tmp # temp files trong memory
volumes:
uploads:
logs:Pattern 3: Volume backup và restore
# Backup: tạo tar từ volume
docker run --rm \
-v pgdata:/data \
-v $(pwd):/backup \
alpine tar czf /backup/pgdata-backup.tar.gz -C /data .
# Restore: giải nén tar vào volume
docker run --rm \
-v pgdata:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/pgdata-backup.tar.gz -C /dataPattern 4: NFS volume cho shared storage giữa nhiều hosts
docker volume create \
--driver local \
--opt type=nfs4 \
--opt o=addr=nfs-server.example.com,rw \
--opt device=:/exports/mydata \
nfs-sharedCác Lỗi Storage Phổ Biến
Lỗi 1: Ghi database data vào writable layer của container
# ❌ SAI — data mất khi container bị remove
docker run -d postgres:16
# ✅ ĐÚNG — data persist
docker run -d -v pgdata:/var/lib/postgresql/data postgres:16Lỗi 2: Dùng bind mounts cho production databases
# ❌ SAI — phụ thuộc host path cụ thể, vấn đề permissions
volumes:
- /home/ubuntu/postgres-data:/var/lib/postgresql/data
# ✅ ĐÚNG — Docker quản lý
volumes:
- pgdata:/var/lib/postgresql/dataLỗi 3: Quên exclude node_modules khỏi bind mounts
# ❌ SAI — node_modules của host ghi đè của container
volumes:
- .:/app
# ✅ ĐÚNG — anonymous volume che node_modules
volumes:
- .:/app
- /app/node_modulesLỗi 4: Không set volume permissions
# Trong Dockerfile — tạo thư mục và set ownership trước khi declare VOLUME
RUN mkdir -p /app/data && chown -R node:node /app/data
USER node
VOLUME /app/dataVí Dụ Hoàn Chỉnh: Full-Stack App với Networking và Storage
# docker-compose.yml
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ssl-certs:/etc/nginx/certs:ro
networks:
- frontend
depends_on:
- api
api:
build:
context: .
dockerfile: Dockerfile
target: production
environment:
DATABASE_URL: postgresql://app:${DB_PASSWORD}@db:5432/myapp
REDIS_URL: redis://cache:6379
volumes:
- uploads:/app/uploads
tmpfs:
- /tmp
networks:
- frontend
- backend
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
interval: 10s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redisdata:/data
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "--pass", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
frontend: # nginx ↔ api
backend: # api ↔ db ↔ cache (nginx KHÔNG thể reach db hay cache trực tiếp)
volumes:
pgdata: # postgres data
redisdata: # redis persistence
uploads: # user uploads
ssl-certs: # TLS certificatesSetup này:
- nginx chỉ nói chuyện được với api (không phải db hay cache trực tiếp)
- api nói chuyện với db và cache bằng tên qua DNS
- Tất cả data volumes persist qua các lần restart container
- Healthchecks đảm bảo các dependent services sẵn sàng trước khi api khởi động
Debug Networking
# Container có đang trên đúng network không?
docker inspect --format='{{json .NetworkSettings.Networks}}' my-container | jq
# Container A có thể reach container B không?
docker exec container-a ping container-b
docker exec container-a curl http://container-b:8080/health
# Container đang listen trên port nào?
docker port my-container
# DNS resolution từ bên trong container
docker exec my-container nslookup db
docker exec my-container cat /etc/resolv.confDebug Storage
# Container có những volumes nào?
docker inspect --format='{{json .Mounts}}' my-container | jq
# Có gì trong volume?
docker run --rm -v my-volume:/data alpine ls -la /data
# Dung lượng volume
docker system df -v
# Kiểm tra permissions bên trong container
docker exec my-container ls -la /app/uploads
# Copy files từ/vào container
docker cp my-container:/app/logs/error.log ./error.log
docker cp ./seed.sql my-container:/tmp/seed.sqlTóm Tắt và Điểm Chính
✅ Custom bridge networks cung cấp automatic DNS — containers tìm nhau bằng tên, không phải IP
✅ Dùng các networks riêng theo từng tầng (frontend/backend/data) để isolate services
✅ Publish ports với prefix 127.0.0.1: trong development để tránh expose ra local network
✅ Volumes do Docker quản lý, portable, được ưu tiên cho production data persistence
✅ Bind mounts tốt cho development (live code reload) — tránh dùng cho production databases
✅ Dùng tmpfs cho sensitive in-memory data không được ghi ra disk
✅ Luôn khai báo named volumes cho stateful services (postgres, redis, elasticsearch)
✅ Trick anonymous volume (- /app/node_modules) ngăn bind mounts ghi đè dependencies trong container
✅ Dùng docker network inspect và docker exec ... ping để debug networking
✅ Volumes bypass storage driver — chúng dùng native filesystem I/O và nhanh hơn writable layer
Series: Docker & Kubernetes Learning Roadmap
Previous: Deep Dive: Dockerfile Best Practices & Multi-Stage Builds
Next: Deep Dive: Kubernetes Workloads (Coming Soon)
Có câu hỏi về Docker networking hay storage? Đừng ngại liên hệ hoặc để lại comment!
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.