Docker Compose 網路設定與服務名稱解析
了解 Compose 預設 bridge 網路、依服務名稱進行 DNS 解析、host mode 適用時機、跨專案共用網路,以及可能繞過 UFW 的 published port。
Compose 在應用程式啟動前建立的內容
Docker Compose 的網路運作遵循一項規則:docker compose up 會為專案建立私有網路,將每個服務連接到該網路,並讓服務彼此透過服務名稱連線。您不需要撰寫任何 networks: 行即可完成這項設定。許多 Compose 網路問題都源自不了解預設網路本來就會自動建立。
以下是一個簡短的檔案。請將它儲存為 compose.yaml,位置放在名為 shop 的目錄中。
services:
web:
image: nginx:1.27
ports:
- "8080:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example啟動專案,查看 Docker 建立的內容:
docker compose up -d
docker network ls現在清單中會有一個名為 shop_default 的網路。Compose 會將其命名為 <project>_default,而專案名稱預設為目錄名稱的小寫形式。您可以使用 docker compose -p myproject up -d 覆寫,或在檔案中加入頂層 name: myproject。其 driver 為 bridge,也就是主機內部的虛擬交換器。每個容器都會取得私有子網路上的位址,對外傳送的網路流量則會在離開時轉換為主機的位址。
docker compose down 會再次刪除該網路。這就是舊專案中的過期容器可能讓網路保持存在的原因:Docker 會以 error while removing network: network shop_default has active endpoints 拒絕操作,而解決方式是停止或移除仍連接至該網路的容器。
如果您剛接觸 Compose,建議先閱讀 Compose 檔案結構與生命週期指令,因為以下內容都假設您已能啟動及停止專案。
依服務名稱進行 DNS 解析,是初學者最容易忽略的部分
在任何使用者定義的 network 上,Docker 都會執行內嵌 DNS server,而每個 container 都能在 127.0.0.11 存取它。此 server 會將 service name 解析為目前 container 的位址。因此,web 不需任何設定,就能透過 hostname db,使用 port 5432 連線到 database。
docker compose exec web getent hosts db這會輸出類似 172.18.0.2 db 的一行內容。如果沒有輸出,表示兩個 service 不在同一個 network 上。
幾乎所有人都會犯一次的錯誤,是在 application config 中使用 localhost。在 container 內,localhost 代表該 container 本身,不是 host,也不是另一個 service。Postgres client 會清楚回報這一點:
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?連線字串應為 postgresql://postgres:example@db:5432/postgres。其中的 host 部分就是 service name。
以下兩個細節能避免之後浪費時間。名稱會解析到目前正在執行的 container,因此 docker compose up -d --scale web=3 可能會以同一個名稱回傳三個位址;若 client 永久快取 DNS,就會固定連到已失效的 container。此外,使用沒有 --network 的一般 docker run 時,舊式的 bridge network 完全無法解析 container name:其中的 container 只能透過 IP address 互相尋找,對外部 hostname 的查詢則會透過 host 的 /etc/resolv.conf 副本進行。這就是 2016 年關於 container link 的建議,與你現在看到的行為不一致的原因。
不需要 ports: 來連接兩個服務
ports: 會將容器埠發布到主機,供來自 Docker 外部的網路流量使用。它與服務對服務的流量無關;同一個 project network 上的容器,原本就能使用完整的埠範圍彼此連線。
因此,許多人加在資料庫服務上的 ports: - "5432:5432" 不但沒有幫助,還會造成實際風險:它會讓 Postgres 暴露在伺服器的公開介面上。請刪除它。如果要讓筆記型電腦在遷移期間連線,請使用 "127.0.0.1:5432:5432" 將其綁定至 loopback,再透過 SSH tunnel 連線。listening socket、published port 與 firewall rule 之間的差異,請參閱Linux 上的埠與 listening service 如何運作。
在 Compose 中,expose: 僅供文件說明使用。它不會開啟任何項目,因為同一個 network 上的容器之間原本就沒有關閉任何連線。
何時適合使用 network_mode host,以及它的代價
Host mode 會移除 container 自己的 network namespace,讓 process 直接使用 host 的 interfaces。
services:
probe:
image: alpine:3.20
network_mode: host
command: sleep infinity使用 host mode 有實際需求。例如,需要查看區域網路上的 broadcast 或 multicast traffic 的 process,例如 media server 或 home automation hub 的裝置探索功能,在 bridge 後方無法看見這些 traffic,因為 bridge 不會將其轉送給 container。讀取 host interface counter 的 monitoring agent 也需要使用 host 的 interfaces。此外,還能略過 address translation 這一跳;在封包速率很高時,這點可能很重要。
代價也很明確。
ports: 會失效。Docker 會警告,使用 host network mode 時會捨棄 published ports,而 container 會使用其 process 實際繫結的 port。兩個 host mode container 若都要使用 port 8080,就會發生衝突,第二個 container 會因 bind: address already in use 而結束。
雙向的依 service name 進行名稱解析都會失效。該 container 不在 project network 上,因此無法解析 db,其他 service 也無法解析它。它仍可透過 host 上 published 的 ports 連到其他 service,通常使用 127.0.0.1。在 Linux 上,它也能直接透過 bridge container 的 IP address,連到該 container 上任何正在監聽的 port,因為 host 可直接存取 bridge network 上的 container,而 host mode container 共用 host 的 network stack。這些位址會在 container 重新建立時變更,因此將 port publish 到 loopback 是較穩定的路徑。
隔離功能也會消失。在 host mode container 內繫結 0.0.0.0 的 process,會監聽 server 的每個 interface,包括公開 interface,行為就像透過 apt 安裝的 package。這也有一項優點:這些 traffic 會遵循一般的 input path,因此 UFW 規則會套用到它;published ports 則不一定如此。
Host mode 是 Linux Docker Engine 的功能。Docker Desktop 只有從 version 4.34 起才支援,且必須先啟用;此外,container 不能繫結 host IP address,而且只處理 TCP 和 UDP。如果團隊一半使用 Linux server,另一半使用 Docker Desktop,請預期同一個檔案可能會有不同的行為。
只有在需要使用 host interfaces 時,才應使用 host mode。不要為了解決 connection problem 而使用它,因為它通常只會把一個問題換成更難處理的問題。
連接兩個 Compose 專案與外部網路
一個專案建立的網路,其他專案看不到。因此,即使反向代理位於同一台伺服器上的 proxy/compose.yaml,也無法看到 app/compose.yaml 中的應用程式。解決方式是建立一個不屬於任何專案的網路。
手動建立一次:
docker network create edge接著在兩個專案中都宣告為外部網路。代理端:
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應用程式端:
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 會告訴 Compose 連接至現有網路,而不是建立新網路,並在 docker compose down 上保留該網路。獨立的 name: 設定比表面上更重要:沒有它,Compose 會尋找名稱完全為 edge 的網路;有了它,便能在檔案中使用一個名稱,在主機上使用另一個名稱。
如果網路不存在,Compose 會拒絕啟動,並回報網路已宣告為外部網路但找不到。請先建立網路。
請注意應用程式檔案如何設定 internal。資料庫只位於該專案的本機網路,因此代理無法連線至資料庫,只有 app 能夠連線。將 internal: true 加入網路設定後,還會完全移除該網路通往外部網路的路由。對資料庫而言,這是很好的預設值,但設定前必須了解一項代價:位於內部網路的容器無法下載任何內容,因此啟動時執行 apt-get update 或 pip install 的 entrypoint 會先卡住,最後因逾時而失敗。
如需包含路由規則與憑證的完整設定範例,請參閱 在單一 Traefik 執行個體後方執行多個應用程式。
已發布的連接埠會繞過 UFW
這是 Compose 網路設定可能演變成安全事件的部分。您發布連接埠後,確認 UFW 已啟用,且除了 SSH 之外一律拒絕連線,但該服務仍可從網際網路存取。
sudo ufw status
curl http://203.0.113.10:8080UFW 顯示該連接埠已封鎖,但 curl 仍會傳回頁面。這不是故障。Docker 會直接在 iptables 中寫入自己的位址轉譯與轉送規則。對已發布容器連接埠的流量會轉送至容器,而不是送往主機,因此不會經過 UFW 管理的本機目的流量 chain。Docker 的規則也會在 UFW 的規則之前比對。
簡單的修正方式是只在需要的位置發布連接埠:
ports:
- "127.0.0.1:8080:80"這會將主機端繫結至 loopback,因此該連接埠可從伺服器本身及 SSH tunnel 存取,其他位置則無法存取。請將公開入口放在 reverse proxy 後方,並明確發布 80 與 443。完整說明,包括必須篩選已發布連接埠時使用的 DOCKER-USER chain,請參閱 為何 Docker 會直接繞過 UFW 發布連接埠,以及如何修正。
如何用 4 個 command 進行偵錯
先確認每個 container 實際位於哪個 network:
docker network inspect shop_defaultContainers 區塊會列出所有已連接的 container 及其位址。缺少某個 service,表示它位於不同的 network、使用 host mode,或尚未執行。
從連接到相同 network 的暫時性 container 測試名稱解析,如此便不需要在自己的 image 內安裝任何工具:
docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432nslookup 失敗表示問題可能出在名稱解析或 network membership。nslookup 成功而 nc 失敗,表示名稱與 network 都正常,問題出在 connection 本身。如果 nc 回報 Connection refused,通常是因為 service 沒有監聽該 port,或是在自己的 container 內監聽 127.0.0.1,而不是 0.0.0.0。這在 development server 中很常見;修正位置是 application 的 bind address,不是 Docker。如果 nc 卡住並逾時,則應尋找途中是否有項目丟棄 traffic,例如 firewall rule,或某個 container 已暫停或沒有回應。
還有一種看起來像 Docker bug 的故障。如果 container 彼此可以通訊,卻無法連到 office 或 VPN network 上的 machine,Docker subnet 很可能與該 network 重疊。Docker 預設會從 172.17.0.0/16 開始配置。請在 /etc/docker/daemon.json 中移動該 pool:
{
"default-address-pools": [
{ "base": "10.200.0.0/16", "size": 24 }
]
}接著執行 sudo systemctl restart docker 並重新建立受影響的 network,因為既有 network 會保留建立時使用的 subnet。
FAQ
為什麼容器無法透過服務名稱互相連線?
因為它們不在同一個 network 上。Compose 會自動將每個服務放在 <project>_default,但只要為服務加入 networks: 清單,該清單就會成為該服務完整的 network 集合,預設 network 不再自動加入。執行 docker network inspect <network>,確認兩個容器都出現在 Containers 區塊中。也請確認兩個服務都未使用 network_mode: host,因為 host mode 容器不在任何 Docker network 上,也無法解析服務名稱。
一個服務要連線到另一個服務時,需要發布連接埠嗎?
不需要。在 Compose network 上,該 network 中的其他容器都能連線到每個容器的所有連接埠。ports: 只用來讓 Docker 外部的 network traffic 進入容器,expose: 則是文件說明。發布資料庫連接埠是常見且代價高昂的做法,因為這會讓資料庫出現在伺服器的公開介面上。
bridge network 與 host network 有何差異?
bridge 會為容器提供獨立的 network namespace,並在虛擬交換器上配置位址,同時提供容器間的自動名稱解析及對外連線的位址轉換。host 則直接使用主機的 network stack:沒有獨立位址、無法透過服務名稱解析、不需要發布連接埠,也不會與主機上的其他監聽服務隔離。bridge 是預設選項,除非程序需要直接使用主機的 network interfaces,否則應使用 bridge。
如何連線來自兩個不同 Compose 檔案的容器?
使用 docker network create edge 建立共用 network,接著在兩個檔案中以 external: true 宣告該 network,並將需要互相通訊的服務加入其中。Compose 不會建立或刪除這個 network。如果略過建立步驟,Compose 會拒絕啟動,並回報該 network 已宣告為 external,但找不到該 network。
為什麼 UFW 封鎖連接埠後,容器仍可從網際網路連線?
因為已發布的連接埠會由 Docker 加入 iptables 的 forwarding rules 處理。這些 rules 會在 UFW 的 rules 之前比對,而且 forwarded traffic 不會通過 UFW 用來篩選 traffic 的 chain。使用 "127.0.0.1:8080:80" 將主機端綁定至 loopback,並將需要公開的服務放在 80 和 443 埠上的反向代理後方。