jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Docker for Developers · Part 4 — Networking: Bridge, Ports & Service Discovery

Theo dõi đường đi mạng Docker: bridge, DNS theo tên service, publish/expose port và kết nối ứng dụng với database.

8 MIN READ Updated JUL 11, 2026

Đây là Phần 4 của chặng nền tảng đưa bạn từ “mới nghe nói về Docker” đến tự tin build, chạy và debug ứng dụng container — Docker, Docker Compose và Kubernetes. Nếu bạn bỏ qua phần lưu dữ liệu, xem lại Phần 3 — Volume, bind mount & biến môi trường. Mỗi phần kết thúc bằng bài tập; hãy làm, đừng chỉ đọc.

Container được cô lập theo thiết kế — mỗi cái có network namespace riêng, nên không thể tự nhiên nói chuyện với host, internet hay container bên cạnh trừ khi bạn nối dây. Phần này trả lời câu thực tế: làm sao chúng nói chuyện với nhau và ra ngoài?

Tư duy senior cho networking: đừng hỏi “IP là gì?” trước; hãy hỏi “hai process có cùng network boundary không, và tên ổn định của process kia là gì?” IP container là chi tiết runtime dễ đổi. Tên service/container trên user-defined network mới là hợp đồng bạn muốn app phụ thuộc.


Mô hình mạng Docker

Mặc định, mọi container tham gia một mạng Ethernet ảo do Docker quản lý trên host. Lưu lượng giữa các container trên cùng bridge ở lại host; ra internet được NAT qua host.

                         HOST (your laptop / server)
 ┌──────────────────────────────────────────────────────────────────┐
 │  Browser / curl  ──► localhost:8080  ──►  port publish (-p)     │
 │                                              │                   │
 │  ┌──────────────────────────────────────────▼─────────────────┐ │
 │  │              docker0  (Linux bridge, 172.17.0.0/16)         │ │
 │  │    ┌─────────────┐              ┌─────────────┐           │ │
 │  │    │  container  │   eth0       │  container  │   eth0    │ │
 │  │    │   web :80   │◄────────────►│   db :5432  │           │ │
 │  │    │ 172.17.0.2  │   same       │ 172.17.0.3  │           │ │
 │  │    └─────────────┘   bridge     └─────────────┘           │ │
 │  └────────────────────────────────────────────────────────────┘ │
 │         ▲ NAT to internet                                         │
 └─────────┼────────────────────────────────────────────────────────┘
           └── physical NIC (Wi‑Fi / ethernet)

Docker chọn network driver khi bạn tạo hoặc gắn mạng:

DriverVai trò
bridge (default)Mạng riêng trên một host; container có NIC ảo.
hostContainer dùng chung stack mạng host — không cô lập, không cần -p.
noneKhông mạng — chỉ lo trong container.
overlayMạng đa host của Docker Swarm. Kubernetes dùng CNI; overlay ở đó thuộc CNI implementation, không phải Docker overlay driver.

Bridge mặc định vs bridge tự tạo

Khi docker run không có --network, Docker gắn container vào mạng bridge sẵn có (thường hiện là bridge trong docker network ls). Đó là default bridge — một LAN chung cho container không chỉ định mạng.

Với app từ hai container trở lên, hãy tạo user-defined bridge:

docker network create appnet

Khác biệt cốt lõi:

Default bridgeUser-defined bridge (e.g. appnet)
DNS by container nameNo — use IP (fragile) or --link (legacy)Yes — Docker embedded DNS resolves db → container IP
Isolation from other stacksWeak — everything lands on one LANStrong — only containers you attach can talk
Best practice for multi-containerAvoidAlways use this

Trên bridge tự tạo, container gọi nhau bằng hostname = tên container (hoặc alias). Trên default bridge, phân giải tên không chạy — bạn sẽ đuổi IP đổi liên tục và thắc mắc vì sao ping db fail.

DEFAULT BRIDGE (docker0) USER-DEFINED: appnet app db name? ✗ no DNS — must use container IPs web db db ✓ embedded DNS — reach by service name :8080 host
On the default bridge, containers have no DNS and must use IPs; on a user-defined bridge, Docker's embedded DNS lets them reach each other by service name

Quản lý mạng

docker network create appnet          # user-defined bridge
docker network ls                     # list networks
docker network inspect appnet         # subnets, connected containers, IPs

docker run -d --name web --network appnet nginx
docker network connect appnet db      # attach a running container
docker network disconnect appnet db
docker network rm appnet              # only if no containers use it

docker network inspect là X-quang: container nào đang cắm, IP nào, gateway là gì. Khi debug “không kết nối được”, inspect cả hai đầu và xác nhận chúng cùng tên mạng.


Publish vs expose cổng

Hai khái niệm hay bị trộn:

  • EXPOSE 5432 trong Dockerfile — chỉ là metadata. Không mở cổng trên host hay đảm bảo container khác truy cập được.
  • -p 8080:80 (publish) — Docker forward từ host:8080container:80. Đây là cách trình duyệt trên laptop tới nginx trong container.
docker run -d --name web -p 8080:80 nginx   # publish — works from host browser
docker run -d --name web --expose 80 nginx  # metadata only — host cannot reach it

localhost trong container là chính container đó, không phải laptop và không phải container khác. Nếu app container dùng postgres://localhost:5432, nó tìm Postgres trong chính nó — thường không có gì. Hãy trỏ tới tên container database trên cùng user network.

Một cách nhớ nhanh: publish port phục vụ traffic từ host vào container; DNS service name phục vụ traffic giữa container với container. Hai đường đi khác nhau, debug bằng hai bộ câu hỏi khác nhau.


Giao tiếp container với container

Ví dụ thực hành: mạng app + Postgres + client psql.

Tạo mạng tự định nghĩa và chạy Postgres:

docker network create appnet

docker run -d \
  --name db \
  --network appnet \
  -e POSTGRES_PASSWORD=secret \
  postgres:16-alpine

Chạy client cùng mạng — kết nối bằng hostname db:

docker run --rm -it --network appnet postgres:16-alpine \
  psql -h db -U postgres -c 'SELECT version();'
# password when prompted: secret

DNS nhúng của Docker phân giải db thành IP của container database trên appnet. Bạn không hard-code 172.18.0.2 — và không nên làm vậy.

Chứng minh default bridge làm hỏng phân giải tên:

docker run -d --name db-default -e POSTGRES_PASSWORD=secret postgres:16-alpine
# no --network → lands on default bridge

docker run --rm -it postgres:16-alpine \
  psql -h db-default -U postgres -c 'SELECT 1;'
# often: could not translate host name "db-default" to address

Cả hai trên default bridge, nhưng db-default không phải tên DNS ở đó — phải docker inspect lấy IP (dễ gãy) hoặc chuyển sang appnet.

  USER-DEFINED appnet                    DEFAULT bridge
 ┌────────┐      DNS: db ──► IP         ┌────────┐     ┌────────┐
 │  app   │ ─────────────────────────► │  db    │     │  db2   │
 └────────┘      hostname works         └────────┘     └────────┘
                                        ping db2 by name → FAIL

Mẫu connection string cho app:

postgres://postgres:secret@db:5432/mydb

                    container NAME on shared user network

Mạng hostnone

Container dùng trực tiếp IP và cổng của host. Tiến trình listen :3000 trong container truy cập được :3000 trên host không cần -p. Đổi lại: không cô lập mạng, trùng cổng với dịch vụ host, hành vi khác Docker Desktop vs Linux. Thỉnh thoảng dùng cho hiệu năng cao hoặc tool cũ trên Linux server.

Chỉ có loopback trong container. Hữu ích cho job batch không cần mạng, hoặc bước nhạy cảm bảo mật không được gọi ra ngoài.


Các bẫy thường gặp

  • Dùng localhost để gọi container khác — sai namespace. Dùng tên dịch vụ trên user network.
  • Lưu IP container — IP đổi khi tạo lại container. Tên DNS trên user bridge ổn định hơn.
  • Quên cho cả hai cùng mạng — docker network connect appnet myapp sửa app đang chạy không cần restart.
  • Kỳ vọng DNS trên default bridge — stack nhiều container nên ở docker network create ..., không phải default ngầm.
  • Publish DB -p 5432:5432 khi dev — tiện cho GUI trên host, nhưng production nên để DB chỉ trong mạng riêng.

Bảng tra nhanh

# networks
docker network create appnet
docker network ls
docker network inspect appnet
docker network connect appnet mycontainer
docker network disconnect appnet mycontainer
docker network rm appnet

# run on a network + publish ports
docker run -d --name web --network appnet -p 8080:80 nginx
docker run -d --name db  --network appnet -e POSTGRES_PASSWORD=s postgres:16-alpine

# debug bằng toolbox riêng — image app/nginx tối giản có thể không có ping/curl
docker run --rm --network appnet busybox:1.37 nslookup db
docker run --rm --network appnet busybox:1.37 ping -c 2 db

# cleanup
docker stop web db && docker rm web db
docker network rm appnet

Bài tập / Exercises

Làm theo thứ tự; mỗi bài củng cố quy tắc bạn sẽ dùng trong Compose (Phần 5).

1. Tạo bridge tên labnet và xác nhận nó tồn tại.

Lời giải
docker network create labnet
docker network ls | grep labnet
docker network inspect labnet   # see driver "bridge", empty containers until you attach some

2. Trên labnet, chạy hai alpine ping-aping-b. Từ ping-a, ping ping-b bằng tên (không dùng IP).

Lời giải
docker run -d --name ping-a --network labnet alpine sleep 3600
docker run -d --name ping-b --network labnet alpine sleep 3600
docker exec ping-a ping -c 3 ping-b
# 3 packets transmitted, 3 received

3. Chạy solo trên default bridge (không --network). Dùng alpine dùng-một-lần thử ping solo — quan sát phân giải tên fail.

Lời giải
docker run -d --name solo alpine sleep 3600
docker run --rm alpine ping -c 1 solo
# ping: bad address 'solo'  (or unknown host) — default bridge has no embedded DNS by name

docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' solo
# use that IP instead — works, but breaks when solo is recreated
docker run --rm alpine ping -c 1 <solo-ip-from-inspect>

4. Publish nginx cổng host 9090curl từ máy bạn (không phải trong container).

Lời giải
docker run -d --name webpub -p 9090:80 nginx
curl -sI http://localhost:9090 | head -n 1
# HTTP/1.1 200 OK

5. Gắn Postgres trên labnet tên db, rồi psql -h db từ client một lần cùng mạng.

Lời giải
docker run -d --name db --network labnet \
  -e POSTGRES_PASSWORD=secret postgres:16-alpine

docker run --rm -it --network labnet postgres:16-alpine \
  psql -h db -U postgres -c "SELECT current_database();"
# enter password: secret

Nâng cao: Chạy webpubdb trên labnet, docker network connect labnet webpub, rồi dùng một toolbox tạm để kiểm tra DNS/kết nối. Đừng giả định image nginx có sẵn ping.

Lời giải
docker network connect labnet webpub
docker run --rm --network labnet busybox:1.37 nslookup db
docker run --rm --network labnet busybox:1.37 ping -c 2 db

Điểm chính

  • Container bị cô lập; mạng là cách bạn cấp kết nối có kiểm soát.
  • User-defined bridgeDNS theo tên container; default bridge thì không.
  • -p host:container publish ra host; EXPOSE chỉ là metadata.
  • localhost trong container không phải host hay container anh em — dùng tên dịch vụ trên user network chung.
  • App nhiều container: docker network create trước, rồi --network (hoặc connect) — đừng trông chờ DNS trên default bridge.

Tiếp theo

Phần 5 — Docker Compose cơ bản: bỏ chuỗi docker run dài — Compose khai báo mạng, volume và image trong một file YAML và bật cả stack bằng docker compose up.