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.
Đâ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:
| Driver | Vai trò |
|---|---|
| bridge (default) | Mạng riêng trên một host; container có NIC ảo. |
| host | Container dùng chung stack mạng host — không cô lập, không cần -p. |
| none | Không mạng — chỉ lo trong container. |
| overlay | Mạ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 bridge | User-defined bridge (e.g. appnet) | |
|---|---|---|
| DNS by container name | No — use IP (fragile) or --link (legacy) | Yes — Docker embedded DNS resolves db → container IP |
| Isolation from other stacks | Weak — everything lands on one LAN | Strong — only containers you attach can talk |
| Best practice for multi-container | Avoid | Always 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 dbfail.
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 5432trong 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:8080→container: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 host và none
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 myappsử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:5432khi 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 some2. Trên labnet, chạy hai alpine ping-a và ping-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 received3. 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 9090 và curl 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 OK5. 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: secretNâng cao: Chạy webpub và db 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 bridge có DNS theo tên container; default bridge thì không.
-p host:containerpublish ra host;EXPOSEchỉ là metadata.localhosttrong 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 createtrước, rồi--network(hoặcconnect) — đừ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.