Skip to content
VDS · Docker và container

Mạng Docker và chuyển tiếp cổng

Cách các container nhìn thấy nhau, publish cổng khác gì so với EXPOSE đơn thuần, và tại sao Docker bỏ qua các quy tắc ufw.

Cập nhật 23 tháng 8, 2026

Một nửa các vấn đề về container đều liên quan đến mạng: một dịch vụ không kết nối được tới cơ sở dữ liệu, một cổng bị chiếm, bảng điều khiển bỗng nhiên bị lộ ra toàn bộ internet. Hãy cùng phân tích cách mọi thứ vận hành.

Ba loại mạng

  • bridge — mặc định. Các container nhận địa chỉ nội bộ và giao tiếp với nhau; chúng đi ra ngoài qua NAT của máy chủ.
  • host — container dùng trực tiếp mạng của máy chủ; việc publish cổng vừa không cần thiết vừa không hoạt động. Nhanh, nhưng không có cách ly: cổng 80 của container chính là cổng 80 của máy chủ.
  • none — hoàn toàn không có mạng. Dành cho những tác vụ không cần mạng.

Một mạng tùy chỉnh do bạn hoặc Compose tạo ra khác với bridge mặc định ở một điểm quan trọng: trong đó có phân giải tên. Container app có thể kết nối tới container db bằng tên db.

docker network create backend
docker network ls
docker network inspect backend

Trong Compose, mạng như vậy được tạo tự động cho mỗi dự án.

Publish cổng

ports:
  - "127.0.0.1:8080:8080"   # accessible only from the machine itself
  - "80:80"                 # accessible to the whole internet

Bên trái là cổng trên máy chủ, bên phải là cổng bên trong container. Chỉ thị EXPOSE trong image không publish bất cứ thứ gì: nó chỉ là một ghi chú về cổng mà ứng dụng lắng nghe.

Nguyên tắc rất đơn giản: chỉ publish những gì bắt buộc phải truy cập được từ bên ngoài. Cơ sở dữ liệu, bảng điều khiển và API nội bộ đặt trên 127.0.0.1, còn máy chủ web có chứng chỉ thì mở ra bên ngoài.

Docker tự viết các quy tắc iptables và làm điều đó trước các chain do ufw quản lý. Một cổng được publish dưới dạng -p 8080:8080 sẽ truy cập được từ internet ngay cả khi ufw hiển thị “deny”. Đừng dựa vào tường lửa — hãy bind cổng vào 127.0.0.1.

Giao tiếp giữa các container

services:
  app:
    networks: [frontend, backend]
  db:
    networks: [backend]
  proxy:
    networks: [frontend]
    ports:
      - "80:80"
      - "443:443"

networks:
  frontend:
  backend:

Ở đây cơ sở dữ liệu chỉ cho ứng dụng truy cập, và chỉ máy chủ web hướng ra bên ngoài. Đây là cấu hình đúng cho mọi stack.

Bên trong container, hãy tham chiếu tới container lân cận bằng tên dịch vụ của nó: postgres://db:5432/app. Địa chỉ localhost bên trong container nghĩa là chính container đó, không phải máy chủ — đây là lỗi phổ biến thứ hai sau latest.

Để kết nối tới một dịch vụ chạy trên chính máy chủ từ trong container, hãy dùng tên host.docker.internal bằng cách thêm:

extra_hosts:
  - "host.docker.internal:host-gateway"

Chẩn đoán

docker compose exec app ping -c2 db
docker compose exec app getent hosts db
sudo ss -tulpn | grep LISTEN
docker port <container>

ss sẽ cho biết cổng thực sự đang lắng nghe trên interface nào: 0.0.0.0:8080 — mở ra bên ngoài, 127.0.0.1:8080 — không.

Những lỗi thường gặp

  • “Port already in use”. Cổng đang bị một container khác hoặc một dịch vụ trên máy chủ chiếm giữ. sudo ss -tulpn | grep :80 sẽ tìm ra thủ phạm.
  • Các container thuộc những dự án Compose khác nhau không thấy nhau. Mỗi dự án có mạng riêng. Hãy kết nối chúng một cách tường minh qua networks: { shared: { external: true } }.
  • Ứng dụng lắng nghe trên 127.0.0.1 bên trong container. Khi đó publish cũng không giúp ích gì: bạn không thể kết nối tới nó từ bên ngoài container. Hãy cấu hình ứng dụng lắng nghe trên 0.0.0.0.
  • Băng thông. Tối đa 500 Mbps chia sẻ trên mỗi máy chủ. Việc phân phối tệp chia sẻ băng thông này với mọi thứ khác.