Cấu hình Docker Internal Network: Cách ly và bảo vệ Database nhiều lớp

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Sai lầm kinh điển: Mở toang port Database ra ngoài VPS

Hồi mới tập tành deploy dự án lên VPS Ubuntu giá rẻ, mình từng dính một vố nhớ đời. Để tiện bật DBeaver từ máy cá nhân vào check data, mình gõ thẳng ports: - "5432:5432" vào service PostgreSQL trong file Compose.

Kết quả chỉ sau 15 phút: log PostgreSQL ngập tràn hàng nghìn request brute-force mật khẩu. Các botnet tự động và crawler từ Shodan, Censys quét dải IP liên tục từng giây.

Nhiều bạn nghĩ bật UFW chặn port là yên tâm. Sai lầm! Docker can thiệp trực tiếp vào iptables bằng chain DOCKER riêng. Quy tắc này chạy trước cả ufw-user-input. Nghĩa là Docker tự động đục thủng tường lửa UFW mà bạn không hề hay biết. Port 5432 lúc này phơi thẳng ra public IP 0.0.0.0.

Nguyên tắc vàng khi chạy production: Database và Backend nếu nằm chung host thì tuyệt đối không mở port ra ngoài. Chúng chỉ cần nói chuyện với nhau trong một mạng nội bộ kín.

Bản chất Docker Network và sức mạnh của cờ internal

Mặc định, Docker Compose tạo ra một custom bridge network có NAT (Network Address Translation). Container vừa giao tiếp được với nhau, vừa thoải mái gửi request ra ngoài internet để tải package hay gọi API.

Cờ internal: true hoạt động ra sao?

Khi thêm thuộc tính internal: true vào cấu hình bridge network, Docker áp dụng cơ chế cách ly triệt để:

  • Không cấp Default Gateway: Container trong mạng này không có default route trỏ ra gateway bên ngoài. Lệnh ping 8.8.8.8 hay curl sẽ báo ngay Network is unreachable.
  • Chặn hoàn toàn traffic hai chiều (Ingress & Egress): Không có rule NAT nào chuyển tiếp gói tin từ card mạng vật lý vào dải IP nội bộ này. Hacker bên ngoài không thể chạm tới container. Chiều ngược lại, container cũng không thể rò rỉ data ra ngoài.
  • Giữ nguyên Docker DNS: Tính năng Service Discovery nội bộ vẫn hoạt động hoàn hảo. Các service chung mạng vẫn gọi nhau qua container name bình thường.

Mô hình Dual-Network (Mạng kép) chuẩn kiến trúc

Để API vừa nhận được request của người dùng, vừa đọc/ghi được database an toàn, ta tách hệ thống thành 2 vùng mạng độc lập:

  1. public_net: Mạng ngoài. Chỉ Nginx/API mở port để đón traffic từ client và gọi API bên thứ ba.
  2. secure_db_net (internal): Mạng nội bộ cách ly tuyệt đối. Chỉ chứa Backend và Database.

Lúc này, Backend đóng vai trò cầu nối khi tham gia đồng thời cả 2 mạng. Ngược lại, Database chỉ cắm đúng 1 chân vào mạng internal, hoàn toàn vô hình trước internet.

Triển khai thực tế với Docker Compose

Dưới đây là kịch bản dựng cụm API Node.js kết nối PostgreSQL được phân vùng mạng nhiều lớp.

Bước 1: Thiết lập docker-compose.yml

version: '3.8'

services:
  api-service:
    image: node:18-alpine
    container_name: production_api
    restart: unless-stopped
    environment:
      - DB_HOST=db-service
      - DB_PORT=5432
      - DB_USER=app_user
      - DB_PASSWORD=VeryStrongSecret_2026
      - DB_NAME=app_production
    networks:
      - public_net
      - secure_db_net
    ports:
      - "3000:3000"

  db-service:
    image: postgres:16-alpine
    container_name: production_postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: VeryStrongSecret_2026
      POSTGRES_DB: app_production
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - secure_db_net
    # Tuyệt đối KHÔNG dùng ports ở đây

networks:
  public_net:
    driver: bridge
  secure_db_net:
    driver: bridge
    internal: true

volumes:
  pgdata:
    driver: local

Bước 2: Khởi động cụm container

Chạy lệnh quen thuộc ở chế độ detached:

docker compose up -d

Kiểm tra xem 2 mạng đã được tạo đúng như thiết kế chưa:

docker network ls

Hệ thống sẽ liệt kê 2 network mới: <tên_thư_mục>_public_net và <tên_thư_mục>_secure_db_net.

Bước 3: Kiểm chứng độ an toàn

Thực tế kiểm chứng là bước quan trọng nhất. Hãy thử chui vào container PostgreSQL và ping thử Google DNS:

docker exec -it production_postgres ping -c 2 8.8.8.8

Terminal sẽ quăng lỗi connect: Network is unreachable. Kịch bản này cực kỳ giá trị: giả sử database dính lỗi bảo mật nghiêm trọng (RCE), kẻ tấn công cũng không thể kéo reverse shell hay tải mã độc từ máy chủ C2 (Command & Control) về VPS.

Tiếp tục đứng từ container API kiểm tra đường truyền sang Postgres:

docker exec -it production_api nc -zv db-service 5432

Thông báo db-service (172.x.x.x:5432) open xuất hiện ngay. Kết nối nội bộ thông suốt 100%.

Lời khuyên khi vận hành

Nếu cần bảo trì hoặc backup dữ liệu bằng công cụ ngoài như DBeaver hay pgAdmin, bạn có 2 cách an toàn hơn hẳn việc mở port:

  • SSH Tunneling: Mở tunnel qua cổng SSH có xác thực SSH Key để map tạm port nội bộ về máy cá nhân.
  • Chạy công cụ trong mạng nội bộ: Dựng chính pgAdmin thành một container nội bộ trong secure_db_net và đặt mật khẩu bảo vệ nhiều lớp.

Chỉ cần 3 dòng cấu hình với internal: true, bạn đã triệt tiêu toàn bộ nguy cơ quét cổng tự động trên production.

Share: