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 存取它。它會將 service name 解析為目前 container 的位址。因此,web 不需任何設定,就能透過 hostname db 的 port 5432 連線至資料庫。
docker compose exec web getent hosts db這會輸出類似 172.18.0.2 db 的一行內容。如果沒有輸出,表示兩個 service 不在同一個 network 上。
幾乎所有人都會犯一次的錯誤,是在應用程式設定中使用 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 會以同一個名稱對應 3 個位址;如果 client 永久快取 DNS,就會一直指向已停止的 container。另一個問題是,未使用 --network 的普通 docker run 所使用的 legacy bridge network 完全不提供名稱解析。因此,2016 年關於 container links 的建議,與你現在看到的行為並不相符。
不需要 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 會移除容器自身的網路命名空間,讓程序直接使用主機的網路介面。
services:
probe:
image: alpine:3.20
network_mode: host
command: sleep infinity使用 host mode 有實際需求。需要查看區域網路上廣播或多點傳送流量的程序,例如媒體伺服器或家庭自動化中樞的裝置探索,無法在 bridge 後方看到這些流量,因為 bridge 不會將這類流量轉送到容器。需要讀取主機網路介面計數器的監控代理程式,也必須使用主機的網路介面。此外,這會略過位址轉譯這一跳,在封包速率很高時可能很重要。
代價也很明確。
ports: 會失效。Docker 警告指出,使用 host network mode 時會捨棄已發布的連接埠,而容器會繫結其程序實際繫結的連接埠。兩個使用 host mode 的容器若都要使用 8080 埠,就會發生衝突,第二個容器會因 bind: address already in use 而終止。
雙向的服務名稱解析都會失效。容器不在專案網路上,因此無法解析 db,其他服務也無法解析它。容器只能透過主機上已發布的連接埠連線到其他服務,通常是 127.0.0.1。
隔離也會消失。在 host mode 容器內繫結 0.0.0.0 的程序,會在伺服器的所有網路介面上監聽,包括公開介面,這與使用 apt 安裝的套件完全相同。這也有一項優點:這類流量會經過正常的輸入路徑,因此 UFW 規則會套用到這些流量;已發布的連接埠則不會。
Host mode 是 Linux Docker Engine 的功能。Docker Desktop 僅從 4.34 版起支援此功能,而且必須先啟用;此外還有限制,容器無法繫結主機 IP 位址,且只處理 TCP 和 UDP。如果團隊一半使用 Linux 伺服器,另一半使用 Docker Desktop,請預期同一個檔案會有不同的行為。
只有在需要主機網路介面時才使用 host mode。不要用它來修復連線問題,因為它通常會把一個問題換成更難處理的問題。
連接兩個 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 個指令進行除錯
先確認每個容器實際連接的網路:
docker network inspect shop_defaultContainers 區塊會列出所有已連接的容器及其位址。未出現在清單中的服務,可能連接到其他網路、使用 host 模式,或尚未執行。
從連接到相同網路的一次性容器測試名稱解析,因此不需要在自己的映像檔中安裝任何工具:
docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432nslookup 失敗表示名稱解析或網路成員資格有問題。nslookup 成功但 nc 失敗,表示服務正在執行,但未監聽該連接埠;或是在自己的容器內監聽 127.0.0.1,而不是 0.0.0.0。後一種情況常見於開發伺服器。修正方式是在應用程式中調整繫結位址,而不是修改 Docker。
還有一種看似 Docker 問題的故障。如果容器彼此可以通訊,但無法連線到辦公室或 VPN 網路中的機器,Docker 子網路可能與該網路重疊。Docker 預設會從 172.17.0.0/16 開始配置。請在 /etc/docker/daemon.json 中移動該網路集區:
{
"default-address-pools": [
{ "base": "10.200.0.0/16", "size": 24 }
]
}接著執行 sudo systemctl restart docker,並重新建立受影響的網路,因為現有網路會保留建立時使用的子網路。
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 埠上的反向代理後方。