SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-10-21

Docker Compose networking: network, DNS và port

Hiểu network mặc định, DNS bằng service name, khi nên dùng host mode, cách dùng chung network giữa project và vì sao port publish có thể bỏ qua UFW.

Docker Compose tạo gì trước khi ứng dụng khởi động

Mạng Docker Compose bắt đầu từ một quy tắc: docker compose up tạo một private network cho project, kết nối mọi service vào đó và cho phép các service này truy cập lẫn nhau bằng service name. Bạn không cần viết một dòng networks: nào để có được điều đó. Phần lớn nhầm lẫn về networking trong Compose xuất phát từ việc không biết rằng mạng mặc định đã được tạo sẵn.

Đây là một file nhỏ. Lưu file này thành compose.yaml trong một thư mục có tên shop.

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example

Khởi động project và xem Docker đã tạo gì:

docker compose up -d
docker network ls

Danh sách hiện có một network tên là shop_default. Compose đặt tên cho network là <project>_default, còn project name mặc định là tên thư mục viết thường. Bạn có thể ghi đè bằng docker compose -p myproject up -d hoặc bằng name: myproject ở cấp cao nhất trong file. Driver của network là bridge, tức một virtual switch bên trong host. Mỗi container nhận một địa chỉ trên private subnet, còn traffic đi ra ngoài được chuyển đổi sang địa chỉ của host.

docker compose down xóa network đó. Đây là lý do một container cũ từ project trước có thể giữ network chưa bị xóa: Docker từ chối thao tác với error while removing network: network shop_default has active endpoints, và cách xử lý là stop hoặc remove container vẫn đang kết nối với network đó.

Nếu bạn chưa quen với Compose, hãy đọc trước bố cục file Compose và các lệnh quản lý vòng đời, vì các phần bên dưới giả định rằng bạn đã biết cách start và stop một project.

DNS theo tên service là phần người mới thường bỏ sót

Trên mọi network do người dùng tạo, Docker chạy một DNS server nhúng mà mỗi container đều thấy tại 127.0.0.11. DNS này phân giải tên service thành địa chỉ hiện tại của container. Vì vậy, web có thể kết nối đến database tại hostname db, trên port 5432, mà không cần cấu hình gì thêm.

docker compose exec web getent hosts db

Lệnh này in ra một dòng như 172.18.0.2 db. Nếu không in gì, hai service không nằm trên cùng network.

Lỗi mà gần như ai cũng mắc một lần là dùng localhost trong cấu hình ứng dụng. Bên trong container, localhost là chính container đó, không phải host và cũng không phải service khác. Postgres client báo lỗi này khá rõ:

could not connect to server: Connection refused
	Is the server running on host "localhost" (127.0.0.1) and accepting
	TCP connections on port 5432?

Connection string phải là postgresql://postgres:example@db:5432/postgres. Phần host là tên service.

Có 2 chi tiết giúp tránh mất thời gian về sau. Tên được phân giải thành những gì đang chạy tại thời điểm đó, nên docker compose up -d --scale web=3 trả về một tên với 3 địa chỉ. Client cache DNS vĩnh viễn sẽ tự cố định vào một container đã chết. Ngoài ra, network bridge kiểu cũ được dùng bởi docker run thông thường khi không có --network không thể phân giải tên container: các container trên đó chỉ tìm thấy nhau bằng địa chỉ IP và phân giải hostname bên ngoài thông qua bản sao của /etc/resolv.conf trên host. Vì vậy, các hướng dẫn về container link từ năm 2016 không khớp với hành vi hiện tại.

Bạn không cần ports: để kết nối hai service

ports: publish một cổng của container trên host. Nó dành cho traffic đi từ bên ngoài Docker vào. Nó không liên quan đến traffic giữa các service; traffic này đã hoạt động trên toàn bộ dải cổng trong project network.

Vì vậy, ports: - "5432:5432" mà nhiều người thêm vào service database không mang lại lợi ích nào mà còn gây hại thực sự: nó expose Postgres trên interface public của server. Hãy xóa nó. Nếu cần truy cập database từ laptop để thực hiện migration, hãy bind cổng đó vào loopback bằng "127.0.0.1:5432:5432" rồi truy cập qua SSH tunnel. Sự khác nhau giữa listening socket, published port và firewall rule được trình bày trong cách port và service đang listen hoạt động trên Linux.

expose: chỉ có tác dụng ghi chú trong Compose. Nó không mở cổng nào, vì giữa các container trên cùng network không có gì bị đóng.

Khi nào nên dùng network_mode host và chi phí phải trả

Host mode bỏ network namespace riêng của container và cho phép process sử dụng trực tiếp các interface của host.

services:
  probe:
    image: alpine:3.20
    network_mode: host
    command: sleep infinity

Có những lý do thực tế để dùng chế độ này. Một process cần nhận broadcast hoặc multicast traffic trên mạng cục bộ, chẳng hạn tính năng phát hiện thiết bị của media server hoặc home automation hub, sẽ không thấy traffic đó khi nằm sau bridge, vì bridge không chuyển loại traffic này đến container. Monitoring agent đọc các interface counter của host cũng cần các interface của host. Ngoài ra, bạn bỏ qua một bước address translation, điều này có ý nghĩa khi packet rate cao.

Chi phí của chế độ này khá cụ thể.

ports: không còn hoạt động. Docker cảnh báo rằng các port đã publish sẽ bị bỏ qua khi dùng host network mode, còn container sẽ bind vào bất kỳ địa chỉ nào mà process của nó bind. Hai container dùng host mode cùng muốn dùng port 8080 sẽ xung đột, và container thứ hai sẽ dừng với lỗi bind: address already in use.

Phân giải tên service không còn hoạt động theo cả 2 chiều. Container không nằm trên project network, nên không thể phân giải db và các service khác cũng không thể phân giải nó. Container vẫn có thể truy cập các service đó qua các port đã publish trên host, thường là tại 127.0.0.1. Trên Linux, container cũng có thể truy cập trực tiếp một container dùng bridge bằng địa chỉ IP của container đó trên bất kỳ port nào đang listening, vì host có quyền truy cập trực tiếp vào các container trên bridge network và container dùng host mode chia sẻ network stack của host. Các địa chỉ này thay đổi khi container được tạo lại, nên publish port trên loopback là cách ổn định hơn.

Isolation không còn. Process bind vào 0.0.0.0 bên trong container dùng host mode sẽ listening trên mọi interface của server, bao gồm cả interface public, giống hệt package được cài bằng apt. Chế độ này vẫn có một điểm có lợi: traffic đó đi qua input path thông thường, nên rule của UFW vẫn áp dụng được. Điều này không đúng với các port đã publish.

Host mode là tính năng của Linux Docker Engine. Docker Desktop chỉ hỗ trợ tính năng này từ version 4.34 trở lên và chỉ sau khi bạn bật nó. Ngoài ra, container không thể bind vào địa chỉ IP của host và chỉ TCP, UDP được xử lý. Nếu một nửa team dùng Linux server còn nửa kia dùng Docker Desktop, hãy dự kiến cùng một file sẽ có hành vi khác nhau.

Chỉ dùng host mode khi bạn cần interface của host. Không dùng nó để sửa lỗi kết nối, vì thường bạn chỉ thay một vấn đề bằng một vấn đề khó xử lý hơn.

Kết nối hai project Compose bằng một network bên ngoài

Network do một project tạo ra không hiển thị với project khác. Vì vậy reverse proxy trong proxy/compose.yaml không thể thấy app trong app/compose.yaml, dù chúng chạy trên cùng một server. Cách khắc phục là dùng một network không thuộc quyền sở hữu của project nào.

Tạo network một lần bằng tay:

docker network create edge

Sau đó khai báo network này là external trong từng project. Phía proxy:

services:
  proxy:
    image: traefik:v3.1
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - edge

networks:
  edge:
    name: edge
    external: true

Phía ứng dụng:

services:
  app:
    image: nginx:1.27
    networks:
      - edge
      - internal
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example
    networks:
      - internal

networks:
  edge:
    name: edge
    external: true
  internal:

external: true cho Compose biết phải gắn vào network đã tồn tại thay vì tạo network mới, đồng thời giữ nguyên network đó trên docker compose down. Key name: riêng biệt quan trọng hơn bạn nghĩ: nếu không có key này, Compose sẽ tìm network có tên chính xác là edge. Khi có key này, bạn có thể đặt một tên cho network trong file và tên khác cho network trên host.

Nếu network không tồn tại, Compose sẽ từ chối khởi động và báo rằng network được khai báo là external nhưng không tìm thấy. Hãy tạo network trước.

Lưu ý cách file ứng dụng xử lý internal. Database chỉ nằm trên network riêng của project đó, nên proxy không thể truy cập database; chỉ app mới có thể truy cập. Thêm internal: true dưới một network sẽ hạn chế thêm và loại bỏ hoàn toàn route của network đó ra Internet. Đây là thiết lập mặc định phù hợp cho database, nhưng cần biết một điểm bất lợi trước khi áp dụng: container trên internal network không thể tải xuống bất kỳ thứ gì, vì vậy entrypoint chạy apt-get update hoặc pip install khi khởi động sẽ bị treo rồi fail do timeout.

Để xem cấu hình hoàn chỉnh có routing rule và certificate, hãy xem chạy nhiều app phía sau một instance Traefik.

Cổng đã publish bypass UFW

Đây là phần trong mạng Compose có thể dẫn đến sự cố bảo mật. Bạn publish một cổng, kiểm tra thấy UFW đang active và deny mọi thứ ngoài SSH, nhưng service vẫn truy cập được từ Internet.

sudo ufw status
curl http://203.0.113.10:8080

UFW báo cổng đã bị block. curl vẫn trả về trang. Không có gì bị hỏng. Docker ghi trực tiếp các rule chuyển đổi địa chỉ và forwarding riêng vào iptables. Traffic đến cổng của container đã publish được forward đến container thay vì chuyển đến host, nên không đi qua chain mà UFW quản lý cho traffic gửi đến máy cục bộ. Các rule của Docker cũng được match trước rule của UFW.

Cách xử lý ngắn gọn là chỉ publish ở nơi cần thiết:

    ports:
      - "127.0.0.1:8080:80"

Cấu hình này bind phía host vào loopback. Vì vậy, cổng chỉ truy cập được từ chính server và qua SSH tunnel, không truy cập được từ nơi khác. Hãy đặt entry point public phía sau reverse proxy, rồi cố ý publish các cổng 80 và 443. Phần giải thích đầy đủ, bao gồm chain DOCKER-USER cho trường hợp bắt buộc phải filter một cổng đã publish, có trong vì sao Docker publish cổng vượt qua UFW và cách xử lý.

Cách debug bằng 4 lệnh

Bắt đầu bằng việc kiểm tra mỗi container thực sự đang nằm trên network nào:

docker network inspect shop_default

Block Containers liệt kê mọi container được attach, cùng với địa chỉ của chúng. Service không xuất hiện trong danh sách đó đang nằm trên network khác, đang dùng host mode hoặc không chạy.

Kiểm tra phân giải tên từ một container tạm thời được attach vào cùng network. Như vậy, bạn không cần cài thêm tool bên trong image của mình:

docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432

nslookup fail cho thấy vấn đề nằm ở phân giải tên hoặc network membership. nslookup thành công nhưng nc fail nghĩa là tên và network đều đúng, còn vấn đề nằm ở chính connection. Nếu nc báo Connection refused, nguyên nhân thường là service không listening trên port đó hoặc đang listening trên 127.0.0.1 bên trong container của nó thay vì 0.0.0.0. Trường hợp cuối thường gặp ở development server. Cách sửa nằm trong bind address của ứng dụng, không phải trong Docker. Nếu nc bị treo rồi timeout, hãy tìm nguyên nhân đang chặn traffic trên đường đi, chẳng hạn firewall rule hoặc container đang bị pause hay không phản hồi.

Có thêm một lỗi trông giống bug của Docker. Nếu các container có thể kết nối với nhau nhưng không thể truy cập một máy trong mạng văn phòng hoặc VPN, subnet của Docker có thể bị trùng với mạng đó. Docker mặc định cấp phát từ 172.17.0.0/16 trở lên. Di chuyển pool trong /etc/docker/daemon.json:

{
  "default-address-pools": [
    { "base": "10.200.0.0/16", "size": 24 }
  ]
}

Sau đó chạy sudo systemctl restart docker và tạo lại các network bị ảnh hưởng, vì network hiện có vẫn giữ subnet được cấp khi nó được tạo.

FAQ

Vì sao các container không thể kết nối với nhau bằng tên service?

Chúng không nằm trên cùng một network. Compose tự động đặt mọi service vào <project>_default, nhưng ngay khi bạn thêm danh sách networks: vào một service, danh sách đó trở thành toàn bộ network của service và network mặc định không còn được ngầm định. Chạy docker network inspect <network> và kiểm tra xem cả hai container có xuất hiện trong khối Containers hay không. Đồng thời kiểm tra để chắc chắn không service nào dùng network_mode: host, vì container chạy ở host mode không nằm trên Docker network nào và không thể phân giải tên service.

Có cần publish port để một service kết nối đến service khác không?

Không. Trên một Compose network, mọi port của mọi container đều có thể được các container khác trên network đó truy cập. ports: chỉ dùng để expose container cho traffic từ bên ngoài Docker, còn expose: chỉ mang tính tài liệu. Publish port của database là một thói quen phổ biến nhưng gây rủi ro và chi phí không cần thiết, vì nó đưa database ra interface public của server.

Bridge và host networking khác nhau như thế nào?

Bridge cấp cho container một network namespace và một địa chỉ riêng trên virtual switch, kèm khả năng phân giải tên tự động giữa các container và cơ chế chuyển đổi traffic đi ra ngoài. Host cho container dùng trực tiếp network stack của host: không có địa chỉ riêng, không phân giải được bằng tên service, không cần publish port và không có isolation với các listener khác trên host. Bridge là mặc định và là lựa chọn phù hợp, trừ khi process cần dùng các interface của host.

Làm cách nào để kết nối các container từ hai file Compose khác nhau?

Tạo một network dùng chung bằng docker network create edge, sau đó khai báo network đó trong cả hai file bằng external: true và attach các service cần kết nối vào network. Compose sẽ không tạo hoặc xóa network này. Nếu bỏ qua bước tạo, Compose sẽ từ chối khởi động và báo network được khai báo là external nhưng không tìm thấy.

Vì sao container vẫn có thể truy cập từ Internet khi UFW chặn port?

Vì port đã publish được xử lý bằng các rule forwarding mà Docker thêm vào iptables. Các rule đó được match trước rule của UFW, và forwarded traffic cũng không đi qua chain mà UFW lọc. Hãy bind phía host vào loopback bằng "127.0.0.1:8080:80" và đặt mọi dịch vụ public phía sau reverse proxy trên các port 80 và 443.

Bài viết này có hữu ích không?