Chapter 04

Networking & Volumes

Docker networks, port mapping, service discovery, volumes và data persistence

Kiến trúc Network

Hệ thống sử dụng 3 Docker networks riêng biệt để cô lập traffic theo nguyên tắc least privilege — mỗi service chỉ truy cập được những gì nó cần:

Internet Port 80/443
frontend-net (bridge)
Nginx:80 / :443
React:80
↕ Nginx cũng thuộc backend-net
backend-net (bridge)
Nginx
ASP.NET API:8080
↕ API cũng thuộc data-net
data-net (internal — no internet)
ASP.NET API
PostgreSQL:5432
Redis:6379
Network data-net được đánh dấu internal: true nghĩa là các container trong network này không có internet access. PostgreSQL và Redis hoàn toàn cô lập — chỉ có API mới kết nối được.

Service Discovery

Docker Compose tự động tạo DNS entries cho mỗi service. Các container giao tiếp với nhau bằng tên service thay vì IP address:

Ví dụ
# Từ container Nginx, kết nối đến React:
proxy_pass http://frontend:80;

# Từ container Nginx, kết nối đến API:
proxy_pass http://backend:8080;

# Từ container API, kết nối đến PostgreSQL:
Host=postgres;Port=5432;Database=myapp_db

# Từ container API, kết nối đến Redis:
redis:6379,password=xxx
Tên service trong docker-compose.yml chính là hostname. Khi bạn đặt service tên postgres, các container khác (cùng network) có thể dùng postgres như hostname để kết nối. Docker DNS tự resolve thành container IP.

expose vs ports

Hai directive quan trọng nhưng thường bị nhầm lẫn:

🔒

expose

Chỉ visible trong Docker network
  • Mở port cho containers khác trong cùng network
  • Không accessible từ host machine
  • Không accessible từ internet
  • Dùng cho: database, cache, internal services
🌐

ports

Map port ra host machine
  • Syntax: "host:container"
  • Accessible từ bên ngoài host
  • Cần thiết cho entry points (Nginx)
  • Giảm thiểu số ports expose ra ngoài
YAML
nginx:
  ports:
    - "80:80"     # Host port 80 → Container port 80 (PUBLIC)
    - "443:443"   # Host port 443 → Container port 443 (PUBLIC)

backend:
  expose:
    - "8080"      # Chỉ visible trong Docker network (INTERNAL)

postgres:
  expose:
    - "5432"      # Chỉ API mới kết nối được (INTERNAL)

redis:
  expose:
    - "6379"      # Chỉ API mới kết nối được (INTERNAL)
Không bao giờ dùng ports cho PostgreSQL hay Redis trong production. Việc expose port 5432 hoặc 6379 ra internet là lỗ hổng bảo mật nghiêm trọng — kẻ tấn công sẽ brute-force password hoặc khai thác vulnerability.

Docker Volumes

Containers là ephemeral — khi bị xóa, dữ liệu bên trong mất theo. Volumes giúp lưu trữ dữ liệu bền vững bên ngoài container lifecycle.

Named Volumes vs Bind Mounts

📦

Named Volumes

Docker quản lý hoàn toàn
  • Docker tự tạo và quản lý vị trí lưu trữ
  • Portable giữa các host
  • Performance tốt hơn trên Linux
  • Dùng cho: database data, cache data
  • Syntax: pgdata:/var/lib/postgresql/data
📁

Bind Mounts

Mount thư mục host vào container
  • Map trực tiếp path trên host machine
  • Dễ truy cập và chỉnh sửa từ host
  • Phụ thuộc vào filesystem của host
  • Dùng cho: config files, SSL certs, logs
  • Syntax: ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
Flag :ro (read-only) ở cuối bind mount nghĩa là container chỉ được đọc file, không được ghi. Luôn dùng :ro cho config files và SSL certs để tránh container vô tình sửa đổi.

Data Persistence theo Service

PostgreSQL — Named Volume Critical +

Volume: pgdata:/var/lib/postgresql/data

Toàn bộ database files (tables, indexes, WAL logs) nằm trong thư mục /var/lib/postgresql/data. Named volume đảm bảo dữ liệu tồn tại ngay cả khi container bị xóa và tạo lại.

Lưu ý: Khi thay đổi POSTGRES_USER hoặc POSTGRES_PASSWORD sau lần chạy đầu tiên, giá trị mới sẽ bị bỏ qua vì PostgreSQL chỉ init database khi volume trống. Để đổi password, dùng SQL: ALTER USER myuser PASSWORD 'newpass';

Redis — Named Volume Important +

Volume: redisdata:/data

Redis mặc định lưu RDB snapshots vào /data/dump.rdb. Khi container restart, Redis tự load snapshot này để khôi phục dữ liệu.

Persistence modes:

  • RDB (mặc định): Snapshot theo interval. Nhanh nhưng có thể mất vài phút dữ liệu gần nhất.
  • AOF: Log mọi write operation. An toàn hơn nhưng file lớn hơn. Bật bằng: --appendonly yes
Nginx — Bind Mounts Config +

Config: ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro

SSL: ./nginx/ssl:/etc/nginx/ssl:ro

Dùng bind mount (không phải volume) vì bạn cần chỉnh sửa config file từ host machine. Flag :ro ngăn container ghi vào file.

Reload config: Sau khi sửa nginx.conf, không cần restart container: docker compose exec nginx nginx -s reload

React & ASP.NET — Không cần Volume Stateless +

React và ASP.NET API là stateless — không lưu dữ liệu trong container. Mọi state đều nằm ở PostgreSQL (persistent) hoặc Redis (cache).

Khi cần update code, chỉ cần build lại image và recreate container — không lo mất dữ liệu.

Quản lý Volumes

Bash
# Liệt kê tất cả volumes
docker volume ls

# Xem chi tiết volume (vị trí lưu trữ trên host)
docker volume inspect myapp_pgdata

# Xem dung lượng volumes
docker system df -v

# Xóa volumes không sử dụng (CẢNH BÁO!)
docker volume prune

# Backup volume PostgreSQL
docker compose exec postgres pg_dump -U myapp_user myapp_db > backup.sql

# Restore từ backup
docker compose exec -T postgres psql -U myapp_user myapp_db < backup.sql
Lệnh docker volume prune xóa tất cả volumes không được sử dụng bởi container nào. Nếu stack đang dừng (docker compose down), volumes sẽ bị coi là "unused" và bị xóa. Hãy cực kỳ cẩn thận!

Docker DNS Resolution

Khi một container cần kết nối đến container khác, quá trình DNS diễn ra như sau:

API gọi "postgres"
Connection string
Docker DNS
127.0.0.11
Resolve IP
172.18.0.x
PostgreSQL container
:5432
Docker DNS server (127.0.0.11) được tự động inject vào mỗi container. Bạn không cần cấu hình gì — chỉ cần dùng service name làm hostname. Nếu container được scale (docker compose up --scale backend=3), DNS sẽ round-robin giữa các instances.