ch02 說過:每個容器有自己的 network namespace——自己的網卡、自己的 IP、自己完整的一組 port 號碼。這解釋了為什麼兩個容器可以同時用 8080,但也帶來新問題:它被關在自己的網路世界裡,那外面要怎麼連進去?裡面又怎麼連到別人?
這一章回答三個你天天遇到的問題:-p 1433:1433 到底改了什麼、為什麼 Compose 裡的服務可以直接用名字互相連線、以及新手第一名的錯誤——在容器裡把資料庫位址寫成 localhost 然後連不到。
容器有自己的 network namespace,等於一個獨立的網路世界,裡面預設只有一張 loopback(lo)網卡。要能對外通訊,Docker 得幫它接線。
eth0,另一端留在宿主機。 容器 A 容器 B
┌─────────┐ ┌─────────┐
│ eth0 │ │ eth0 │ ← 各自 namespace 裡的網卡
│172.17.0.2│ │172.17.0.3│
└────┬────┘ └────┬────┘
│ veth pair │ veth pair ← 虛擬網路線
═════╪════════════════════╪══════════ 宿主機
└────────┬───────────┘
┌────┴─────┐
│ docker0 │ 172.17.0.1 ← 虛擬交換器(也是容器的預設閘道)
└────┬─────┘
│ NAT(偽裝成宿主機 IP)
┌────┴─────┐
│ eth0 │ 192.168.1.50 ← 宿主機真實網卡
└──────────┘ → 外部網路apt-get install),但外面的人不知道它存在。172.17.0.2 是宿主機內部的私有位址,外部網路根本沒有到那裡的路由。這就是為什麼需要 port mapping。-p 幾乎是唯一的連線方式。回到你天天在打的那行設定:
ports:
- '1433:1433' # 等同於 docker run -p 1433:1433所以當你用 SSMS 連 localhost:1433 時,封包的旅程是:
SSMS → 宿主機 localhost:1433
→ iptables DNAT 規則改寫目的地
→ docker0 → veth → 容器的 eth0:1433
→ SQL Server 收到-p 宿主機埠:容器埠——左邊是「外面」,右邊是「裡面」。-p 14330:1433 表示「你在自己電腦上連 14330,會被轉進容器的 1433」。當你本機已經裝了 SQL Server 佔用 1433 時,就用這招換一個對外的埠——容器裡面完全不用改設定。-p 8080:80 # 綁定宿主機所有網路介面 ⚠️ 同網段的人都連得到
-p 127.0.0.1:8080:80 # 只綁 localhost ✅ 只有本機連得到(開發時建議)
-p 80 # 只寫容器埠 → 宿主機隨機挑一個高位埠
-P # 大寫 P:把映像 EXPOSE 的埠全部隨機映射-p 5432:5432 預設綁在 0.0.0.0,代表同一個區域網路內的任何人都能連到你的資料庫。而且 Docker 的 iptables 規則可能繞過你設定的 ufw/firewalld 防火牆——你以為防火牆擋住了,其實沒有。開發環境請養成寫 127.0.0.1: 前綴的習慣。EXPOSE 8080(Dockerfile 裡):純粹是文件,宣告「這個映像的服務在 8080」。它不會打開任何埠,不加 -p 一樣連不進去。-p(執行時):真正建立轉接規則的那個。-P(大寫)自動映射,以及讓人知道該開哪個埠。Docker 有一個預設的 bridge 網路(就是 docker0),但你幾乎永遠不該用它——因為它少了一個關鍵功能。
# 手動建立自訂網路
docker network create my-net
docker run -d --name sqlserver --network my-net mcr.microsoft.com/mssql/server:2022-latest
docker run -d --name app --network my-net myapp:1.0
# 現在 app 容器裡可以直接:
# jdbc:sqlserver://sqlserver:1433;databaseName=mydb
# ↑ 用名字,不用 IPdocker compose up,它都會建立一個專屬的自訂網路,並把所有服務加進去。所以 compose 檔裡的服務名稱(sqlserver、redis)天生就是可以直接連的主機名稱——這就是 ch01 說的「compose 悄悄做的六件事」之一。services:
frontend:
networks: [front-net]
backend:
networks: [front-net, back-net] # 兩邊都通,當橋樑
database:
networks: [back-net] # 只有 backend 連得到
networks:
front-net:
back-net:不同網路的容器彼此完全看不到。上面這個配置讓資料庫只暴露給後端,前端就算被入侵也碰不到 DB——這是把 AWS 的公有/私有子網概念(AWS ch06)縮小到單機上實作。
--network host:不建立 network namespace,直接用宿主機的網路。容器監聽 8080 就是宿主機的 8080,不需要 -p。優點是沒有 NAT 開銷(效能敏感時用),缺點是完全沒有網路隔離、埠會衝突。注意:在 Docker Desktop(Mac/Windows)上行為和 Linux 不同,別依賴它。--network none:只有 loopback,完全不連外。適合跑純計算、不需要網路的批次任務(安全性最高)。這是容器新手第一名的錯誤,幾乎每個人都踩過一次。
# docker-compose.yml
services:
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
ports: ['1433:1433']
app:
image: myapp:1.0
environment:
# ❌ 錯誤:從容器內看,localhost 是「這個容器自己」
SPRING_DATASOURCE_URL: jdbc:sqlserver://localhost:1433;databaseName=mydb啟動後 app 立刻噴 Connection refused。
localhost 在容器裡指的是「這個容器自己的 network namespace」,不是你的電腦、也不是別的容器。app 容器對自己的 1433 埠喊話,而它自己根本沒有跑資料庫——當然被拒絕。 # ✅ 正確
SPRING_DATASOURCE_URL: jdbc:sqlserver://sqlserver:1433;databaseName=mydb
# ↑服務名 ↑容器內部埠假設你為了避開本機衝突改成 ports: ['14330:1433']:
localhost:14330(走 port mapping 的外部埠)sqlserver:1433(走內部網路,完全不經過 port mapping,所以用容器內部的原始埠)-p,你寫 -p 純粹是為了讓自己(宿主機)能連進去。如果你的資料庫是直接裝在自己電腦上(不是容器),容器裡要怎麼連?
host.docker.internal(Docker Desktop 內建;Linux 上需要加 --add-host=host.docker.internal:host-gateway)。jdbc:sqlserver://host.docker.internal:1433;...服務名稱host.docker.internallocalhost(這正是它唯一正確的用法)網路問題最怕亂猜。照這個順序查,幾乎都能在五分鐘內定位。
docker compose ps # STATUS 是 Up 還是 Exited/Restarting?
docker compose logs sqlserver --tail 50最常見的真相:資料庫容器根本沒起來(密碼太弱被 SQL Server 拒絕、記憶體不足、EULA 沒接受)。連線失敗只是症狀,不是病因。
docker network ls
docker network inspect <網路名> # Containers 區段列出成員不同 compose 專案的服務預設在不同網路,彼此看不到——這是跨專案連線失敗的頭號原因。
docker compose exec app ping -c 2 sqlserver
# 或(精簡映像沒有 ping 時)
docker compose exec app getent hosts sqlserverdocker compose exec sqlserver ss -lntp # 看容器內監聽的埠
# 或從 app 容器測試連通性
docker compose exec app nc -zv sqlserver 1433127.0.0.1(只接受本機連線)。在容器裡這代表只有它自己連得到,別的容器一律拒絕。容器化的服務必須監聽 0.0.0.0(所有介面)。如果你看到 ss 輸出是 127.0.0.1:8080 而不是 0.0.0.0:8080 或 *:8080,這就是原因。server.address=127.0.0.1 就會中招。depends_on 只保證「容器被建立」,不保證「服務已經可以接受連線」。SQL Server 從容器啟動到能接受連線要好幾十秒,你的 app 早就衝過去連了。condition: service_healthy,以及應用層的連線重試)。# 精簡映像裡沒有網路工具時,開一個工具容器加入同一個網路
docker run --rm -it --network <網路名> nicolaka/netshoot bash
# 裡面有 ping / dig / curl / ss / tcpdump / nmap 一應俱全| 情境 | 用什麼位址 | 用哪個埠 | 為什麼 |
|---|---|---|---|
| 容器 A 連容器 B(同網路) | B 的服務名稱 | B 的容器內部埠 | 走自訂網路的內建 DNS,完全不經過 port mapping |
| 你的電腦連容器 | localhost | -p 左邊那個宿主機埠 | 走 iptables DNAT 轉接規則 |
| 容器連宿主機上的服務 | host.docker.internal | 宿主機上該服務的埠 | Docker 提供的特殊名稱(Linux 需 --add-host) |
| 容器連自己 | localhost | 自己監聽的埠 | 每個容器有自己的 network namespace 與 localhost |
| 模式 | 隔離程度 | 怎麼被連到 | 適用場景 |
|---|---|---|---|
| 預設 bridge | 有 namespace 隔離 | 需要 -p;容器間只能用 IP | 幾乎不該用(沒有 DNS) |
| 自訂 bridge | 有隔離,且可切多個網段 | 需要 -p;容器間可用名稱 | 標準做法(Compose 自動建立) |
| host | 無(直接用宿主機網路) | 不需 -p,直接就是宿主機的埠 | 極致效能需求;會有埠衝突風險 |
| none | 最高(只有 loopback) | 連不到,也連不出去 | 純計算批次任務 |
| overlay | 跨主機的虛擬網段 | 跨機器的容器可直接互連 | Swarm / K8s 多節點(ch14) |
| 症狀 | 最可能原因 | 怎麼確認 |
|---|---|---|
| Connection refused(容器間) | 位址寫成 localhost,應該用服務名稱 | 檢查連線字串;ping 服務名試試 |
| Unknown host | 不在同一個網路(常見於跨 compose 專案) | docker network inspect 看成員清單 |
| 第一次啟動失敗,重啟就好 | depends_on 只等容器建立,不等服務就緒 | 看 app 的失敗時間點是否早於 DB 就緒(ch11) |
| 解析得到但連不上 | 服務只監聽 127.0.0.1,或根本沒起來 | docker exec ... ss -lntp 看監聽位址 |
| 宿主機連不進去 | 忘了 -p,或映射的埠寫反了 | docker ps 的 PORTS 欄位 |
| 同事連得到你的 DB | -p 預設綁 0.0.0.0,整個區網都連得到 | 改成 -p 127.0.0.1:1433:1433 |
點擊卡片翻面查看答案,共 11 張。